Compare commits

..
Author SHA1 Message Date
Calum H.andGitHub a77e4af0ed Merge branch 'main' into test 2026-06-26 17:23:35 +01:00
Michael H. d019ac7fcf test: remove blacksmith 2026-06-26 17:45:37 +02:00
Michael H. 076aaac3eb test: remove mold 2026-06-26 17:40:16 +02:00
Michael H. 7ae71d04a6 test: remove sccache 2026-06-26 17:31:45 +02:00
390 changed files with 7300 additions and 28424 deletions
-18
View File
@@ -73,10 +73,6 @@ jobs:
docker:
runs-on: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
env:
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
needs: [skip-if-clean]
if: ${{ needs.skip-if-clean.outputs.skip != 'true' }}
steps:
@@ -89,9 +85,6 @@ jobs:
rustflags: ''
cache: false
- name: Setup mold
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Cache Cargo registry and index
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
@@ -101,17 +94,6 @@ jobs:
~/.cargo/bin
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Mount sccache disk cache
if: needs.skip-if-clean.outputs.internal == 'true'
uses: useblacksmith/stickydisk@13af8883542ca949a717e70fef89d15edbb29d88 # v1.2.0
with:
key: ${{ github.repository }}-daedalus-sccache
path: /mnt/sccache
- name: Setup sccache
if: needs.skip-if-clean.outputs.internal == 'true'
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- name: Build daedalus_client
run: cargo build --release --package daedalus_client
+4 -23
View File
@@ -76,10 +76,6 @@ jobs:
env:
SQLX_OFFLINE: 'true'
GIT_HASH: ${{ github.sha }}
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
RUST_MIN_STACK: '16777216'
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -90,15 +86,11 @@ jobs:
rustflags: ''
cache: false
- name: Setup mold
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake libcurl4-openssl-dev
#uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
#with:
# packages: cmake libcurl4-openssl-dev
# version: v2 # cache key
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
with:
packages: cmake libcurl4-openssl-dev
version: v2 # cache key
- name: Cache Cargo registry and index
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
@@ -109,17 +101,6 @@ jobs:
~/.cargo/bin
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Mount sccache disk cache
if: needs.skip-if-clean.outputs.internal == 'true'
uses: useblacksmith/stickydisk@13af8883542ca949a717e70fef89d15edbb29d88 # v1.2.0
with:
key: ${{ github.repository }}-labrinth-sccache
path: /mnt/sccache
- name: Setup sccache
if: needs.skip-if-clean.outputs.internal == 'true'
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- name: Build labrinth
run: cargo build --profile release-labrinth --package labrinth
-17
View File
@@ -44,16 +44,6 @@ jobs:
name: Build
env:
VITE_STRIPE_PUBLISHABLE_KEY: pk_live_51JbFxJJygY5LJFfKLVVldb10HlLt24p421OWRsTOWc5sXYFOnFUXWieSc6HD3PHo25ktx8db1WcHr36XGFvZFVUz00V9ixrCs5
# SCCACHE_DIR: '/mnt/sccache'
# SCCACHE_CACHE_SIZE: '10G'
# SCCACHE_MULTILEVEL_CHAIN: 'disk,s3'
SCCACHE_S3_KEY_PREFIX: '${{ github.repository }}/'
SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }}
SCCACHE_REGION: ${{ secrets.SCCACHE_REGION }}
SCCACHE_ENDPOINT: ${{ secrets.SCCACHE_ENDPOINT }}
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }}
RUSTC_WRAPPER: 'sccache'
strategy:
fail-fast: false
matrix:
@@ -84,13 +74,6 @@ jobs:
rustflags: ''
target: ${{ contains(matrix.platform, 'macos') && 'x86_64-apple-darwin' || '' }}
- name: Setup mold
if: contains(matrix.platform, 'ubuntu')
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Setup sccache
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- name: Enable Corepack
run: corepack enable
+1 -35
View File
@@ -18,25 +18,11 @@ jobs:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check.outputs.skip }}
internal: ${{ steps.check-internal.outputs.internal }}
if: ${{ always() }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check if workflow runs on an internal branch
id: check-internal
env:
EVENT_NAME: ${{ github.event_name }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
REPO: ${{ github.repository }}
run: |
if [ "$EVENT_NAME" != "pull_request" ] || [ "$HEAD_REPO" = "$REPO" ]; then
echo "internal=true" >> $GITHUB_OUTPUT
else
echo "internal=false" >> $GITHUB_OUTPUT
fi
- name: Merge Queue CI Check Skipper
id: merge-queue-ci-skipper
uses: ./.github/merge-queue-ci-skipper
@@ -62,7 +48,7 @@ jobs:
build:
name: Lint and Test
runs-on: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
runs-on: ubuntu-latest
needs: [skip-if-clean]
if: ${{ needs.skip-if-clean.outputs.skip != 'true' }}
@@ -76,12 +62,6 @@ jobs:
# since we don't want warnings to become errors
# while developing)
RUSTFLAGS: -Dwarnings
# sccache config (only populated for internal branches; secrets aren't
# available to forked PRs, and blacksmith stickydisk requires a
# blacksmith runner)
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
steps:
- name: Check out code
@@ -122,9 +102,6 @@ jobs:
components: clippy, rustfmt
cache: false
- name: Setup mold
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Cache Cargo registry and index
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae #v5.0.5
with:
@@ -134,17 +111,6 @@ jobs:
~/.cargo/bin
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Mount sccache disk cache
if: needs.skip-if-clean.outputs.internal == 'true'
uses: useblacksmith/stickydisk@13af8883542ca949a717e70fef89d15edbb29d88 # v1.2.0
with:
key: ${{ github.repository }}-turbo-sccache
path: /mnt/sccache
- name: Setup sccache
if: needs.skip-if-clean.outputs.internal == 'true'
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- name: Setup binstall
uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 # v1.18.1
Generated
+64 -147
View File
@@ -59,9 +59,9 @@ dependencies = [
[[package]]
name = "actix-http"
version = "3.13.1"
version = "3.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2"
checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49"
dependencies = [
"actix-codec",
"actix-rt",
@@ -75,7 +75,7 @@ dependencies = [
"derive_more 2.1.1",
"encoding_rs",
"flate2",
"foldhash 0.2.0",
"foldhash 0.1.5",
"futures-core",
"h2 0.3.27",
"http 0.2.12",
@@ -87,8 +87,8 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
"rand 0.10.1",
"sha1 0.11.0",
"rand 0.9.2",
"sha1",
"smallvec",
"tokio",
"tokio-util",
@@ -146,9 +146,9 @@ dependencies = [
[[package]]
name = "actix-router"
version = "0.5.4"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
dependencies = [
"bytestring",
"cfg-if",
@@ -209,9 +209,9 @@ dependencies = [
[[package]]
name = "actix-web"
version = "4.14.0"
version = "4.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4"
checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea"
dependencies = [
"actix-codec",
"actix-http",
@@ -228,7 +228,7 @@ dependencies = [
"cookie 0.16.2",
"derive_more 2.1.1",
"encoding_rs",
"foldhash 0.2.0",
"foldhash 0.1.5",
"futures-core",
"futures-util",
"impl-more",
@@ -244,7 +244,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"smallvec",
"socket2 0.6.1",
"socket2 0.5.10",
"time",
"tracing",
"url",
@@ -1127,7 +1127,7 @@ dependencies = [
"http-body-util",
"md-5",
"pin-project-lite",
"sha1 0.10.6",
"sha1",
"sha2",
"tracing",
]
@@ -1499,7 +1499,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest 0.10.7",
"digest",
]
[[package]]
@@ -1511,15 +1511,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "block2"
version = "0.5.1"
@@ -2233,12 +2224,6 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "const-random"
version = "0.1.18"
@@ -2439,7 +2424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d"
dependencies = [
"crc",
"digest 0.10.7",
"digest",
"rustversion",
"spin 0.10.0",
]
@@ -2530,15 +2515,6 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "cssparser"
version = "0.29.6"
@@ -2815,7 +2791,7 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid 0.9.6",
"const-oid",
"pem-rfc7468",
"zeroize",
]
@@ -2928,23 +2904,12 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer 0.10.4",
"const-oid 0.9.6",
"crypto-common 0.1.6",
"block-buffer",
"const-oid",
"crypto-common",
"subtle",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer 0.12.1",
"const-oid 0.10.2",
"crypto-common 0.2.2",
]
[[package]]
name = "directories"
version = "6.0.0"
@@ -3162,7 +3127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest 0.10.7",
"digest",
"elliptic-curve",
"rfc6979",
"signature",
@@ -3186,7 +3151,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest 0.10.7",
"digest",
"ff",
"generic-array",
"group",
@@ -4437,7 +4402,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.7",
"digest",
]
[[package]]
@@ -4567,15 +4532,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hybrid-array"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
dependencies = [
"typenum",
]
[[package]]
name = "hyper"
version = "0.14.32"
@@ -4937,9 +4893,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
[[package]]
name = "impl-more"
version = "0.3.1"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870"
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
[[package]]
name = "indenter"
@@ -5434,12 +5390,11 @@ dependencies = [
"rust_iso3166",
"rustls 0.23.32",
"rusty-money",
"scalar_api_reference",
"sentry",
"serde",
"serde_json",
"serde_with",
"sha1 0.10.6",
"sha1",
"sha2",
"spdx",
"sqlx",
@@ -5456,6 +5411,8 @@ dependencies = [
"url",
"urlencoding",
"utoipa",
"utoipa-actix-web",
"utoipa-scalar",
"uuid 1.23.3",
"validator",
"webauthn-rs",
@@ -5865,7 +5822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest 0.10.7",
"digest",
]
[[package]]
@@ -8537,8 +8494,8 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
dependencies = [
"const-oid 0.9.6",
"digest 0.10.7",
"const-oid",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
@@ -8551,40 +8508,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-embed"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.106",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -8904,18 +8827,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scalar_api_reference"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d67c85edd56c41f364603eff688a330f01dee99bdfc84c005d3bc3d0721d6b2"
dependencies = [
"actix-web",
"rust-embed",
"serde",
"serde_json",
]
[[package]]
name = "scc"
version = "2.4.0"
@@ -9341,15 +9252,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.150"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -9544,18 +9455,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest 0.10.7",
]
[[package]]
name = "sha1"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"digest 0.11.3",
"digest",
]
[[package]]
@@ -9572,7 +9472,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest 0.10.7",
"digest",
]
[[package]]
@@ -9605,7 +9505,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest 0.10.7",
"digest",
"rand_core 0.6.4",
]
@@ -9882,7 +9782,7 @@ dependencies = [
"bytes",
"chrono",
"crc",
"digest 0.10.7",
"digest",
"dotenvy",
"either",
"futures-channel",
@@ -9903,7 +9803,7 @@ dependencies = [
"rsa",
"rust_decimal",
"serde",
"sha1 0.10.6",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
@@ -11390,7 +11290,7 @@ dependencies = [
"constant_time_eq",
"hmac",
"rand 0.9.2",
"sha1 0.10.6",
"sha1",
"sha2",
]
@@ -11605,7 +11505,7 @@ dependencies = [
"rand 0.9.2",
"rustls 0.23.32",
"rustls-pki-types",
"sha1 0.10.6",
"sha1",
"thiserror 2.0.17",
"utf-8",
]
@@ -11624,9 +11524,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.20.1"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "uds_windows"
@@ -11857,6 +11757,17 @@ dependencies = [
"utoipa-gen",
]
[[package]]
name = "utoipa-actix-web"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7eda9c23c05af0fb812f6a177514047331dac4851a2c8e9c4b895d6d826967f"
dependencies = [
"actix-service",
"actix-web",
"utoipa",
]
[[package]]
name = "utoipa-gen"
version = "5.4.0"
@@ -11871,6 +11782,18 @@ dependencies = [
"uuid 1.23.3",
]
[[package]]
name = "utoipa-scalar"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
dependencies = [
"actix-web",
"serde",
"serde_json",
"utoipa",
]
[[package]]
name = "uuid"
version = "0.8.2"
@@ -13527,12 +13450,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zopfli"
version = "0.8.2"
+2 -1
View File
@@ -158,7 +158,6 @@ rust-s3 = { version = "0.37.0", default-features = false, features = [
] }
rustls = "0.23.32"
rusty-money = "0.4.1"
scalar_api_reference = { version = "0.2.2", default-features = false }
secrecy = "0.10.3"
sentry = { version = "0.45.0", default-features = false, features = [
"backtrace",
@@ -218,6 +217,8 @@ typed-path = "0.12.0"
url = "2.5.7"
urlencoding = "2.1.3"
utoipa = { version = "5.4.0", features = ["actix_extras", "chrono", "decimal"] }
utoipa-actix-web = { version = "0.1.2" }
utoipa-scalar = { version = "0.3.0", default-features = false }
uuid = "1.18.1"
validator = "0.20.0"
webauthn-rs = "0.5.5"
@@ -14,7 +14,31 @@
<UnplugIcon class="text-secondary" />
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
</div>
<AppUpdateButton />
<ButtonStyled color="brand" type="outlined" hover-color-fill="background">
<button
v-if="showUpdatePill"
type="button"
class="!h-[34px] overflow-hidden text-sm !transition-[width,opacity,transform,background-color,color,filter] !duration-200 ease-out"
:class="[
updatePillWidthClass,
{
'update-pill-ready-hidden': finishedDownloading && !animateReadyPill,
'update-pill-ready-visible': finishedDownloading && animateReadyPill,
},
]"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" :class="{ 'animate-spin': restarting }" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">{{ downloadPercent }}%</span>
</span>
<span v-else>{{ updateLabel }}</span>
</button>
</ButtonStyled>
<div
class="flex border-solid border-surface-5 text-sm items-center gap-2 py-1.5 px-3 rounded-xl border"
>
@@ -120,6 +144,7 @@ import {
DownloadIcon,
DropdownIcon,
OnlineIndicatorIcon,
RefreshCwIcon,
StarIcon,
StopCircleIcon,
TerminalSquareIcon,
@@ -136,10 +161,9 @@ import {
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { Dropdown } from 'floating-vue'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import AppUpdateButton from '@/components/ui/app-update-button/index.vue'
import { useInstallJobNotifications } from '@/composables/browse/install-job-notifications'
import { trackEvent } from '@/helpers/analytics'
import { loading_listener, process_listener } from '@/helpers/events'
@@ -148,6 +172,11 @@ import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/p
import type { LoadingBar } from '@/helpers/state'
import { progress_bars_list } from '@/helpers/state'
import type { GameInstance } from '@/helpers/types'
import {
appUpdateState,
downloadAvailableAppUpdate,
installAvailableAppUpdate,
} from '@/providers/app-update'
const { handleError } = injectNotificationManager()
const popupNotificationManager = injectPopupNotificationManager()
@@ -212,8 +241,96 @@ const messages = defineMessages({
id: 'app.action-bar.view-active-downloads',
defaultMessage: 'View active downloads',
},
update: {
id: 'app.action-bar.update',
defaultMessage: 'Update',
},
downloadingUpdate: {
id: 'app.action-bar.downloading-update',
defaultMessage: 'Downloading update',
},
reloadToUpdate: {
id: 'app.action-bar.reload-to-update',
defaultMessage: 'Reload to update',
},
})
const {
downloading,
downloadPercent,
downloadProgress,
finishedDownloading,
isVisible: isUpdateVisible,
metered,
restarting,
} = appUpdateState
const isUpdateDownloading = computed(
() =>
downloading.value ||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
)
const showUpdatePill = computed(
() => isUpdateVisible.value && (finishedDownloading.value || metered.value),
)
const animateReadyPill = ref(false)
const updateLabel = computed(() => {
if (isUpdateDownloading.value) {
return formatMessage(messages.downloadingUpdate)
}
if (finishedDownloading.value) {
return formatMessage(messages.reloadToUpdate)
}
return formatMessage(messages.update)
})
const updatePillWidthClass = computed(() => {
if (isUpdateDownloading.value) {
return 'w-[219px]'
}
if (finishedDownloading.value) {
return 'w-[166px]'
}
return '!w-[96px]'
})
let readyPillAnimationFrame: number | null = null
watch([showUpdatePill, finishedDownloading], async ([show, ready], [wasShown, wasReady]) => {
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
readyPillAnimationFrame = null
}
if (!show || !ready) {
animateReadyPill.value = false
return
}
if (wasShown && wasReady) {
return
}
animateReadyPill.value = false
await nextTick()
readyPillAnimationFrame = requestAnimationFrame(() => {
animateReadyPill.value = true
readyPillAnimationFrame = null
})
})
async function handleUpdateClick() {
if (isUpdateDownloading.value) {
return
}
if (finishedDownloading.value) {
await installAvailableAppUpdate()
} else {
await downloadAvailableAppUpdate()
}
}
const currentProcesses = ref<RunningProcess[]>([])
const selectedProcess = ref<RunningProcess | undefined>()
@@ -502,5 +619,20 @@ onBeforeUnmount(() => {
unlistenProcess()
unlistenLoading()
installJobNotifications.dispose()
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
}
})
</script>
<style scoped>
.update-pill-ready-hidden {
opacity: 0;
transform: scale(0.96);
}
.update-pill-ready-visible {
opacity: 1;
transform: scale(1);
}
</style>
@@ -1,32 +1,28 @@
<script setup>
import { XIcon } from '@modrinth/assets'
import { WrenchIcon, XIcon } from '@modrinth/assets'
import {
Accordion,
ButtonStyled,
Checkbox,
commonMessages,
defineMessages,
FileTreeSelect,
injectNotificationManager,
NewModal,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { save } from '@tauri-apps/plugin-dialog'
import { readDir, stat } from '@tauri-apps/plugin-fs'
import { ref } from 'vue'
import { PackageIcon } from '@/assets/icons'
import {
export_instance_mrpack,
get_full_path,
get_pack_export_candidates,
} from '@/helpers/instance'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { export_instance_mrpack, get_pack_export_candidates } from '@/helpers/instance'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const messages = defineMessages({
header: { id: 'app.export-modal.header', defaultMessage: 'Export modpack' },
modpackNameLabel: { id: 'app.export-modal.modpack-name-label', defaultMessage: 'Modpack name' },
modpackNameLabel: { id: 'app.export-modal.modpack-name-label', defaultMessage: 'Modpack Name' },
modpackNamePlaceholder: {
id: 'app.export-modal.modpack-name-placeholder',
defaultMessage: 'Modpack name',
@@ -43,7 +39,15 @@ const messages = defineMessages({
id: 'app.export-modal.description-placeholder',
defaultMessage: 'Enter modpack description...',
},
selectFilesLabel: {
id: 'app.export-modal.select-files-label',
defaultMessage: 'Configure which files are included in this export',
},
exportButton: { id: 'app.export-modal.export-button', defaultMessage: 'Export' },
includeFile: {
id: 'app.export-modal.include-file-accessibility-label',
defaultMessage: 'Include "{file}"?',
},
})
const props = defineProps({
@@ -55,9 +59,8 @@ const props = defineProps({
defineExpose({
show: () => {
resetExportState()
exportModal.value.show()
void initFiles().catch(handleError)
initFiles()
},
})
@@ -66,33 +69,62 @@ const nameInput = ref(props.instance.name)
const exportDescription = ref('')
const versionInput = ref('1.0.0')
const files = ref([])
const selectedFilePaths = ref([])
const fileTreeKey = ref(0)
const filesLoadId = ref(0)
const instanceRoot = ref('')
const loadedDirectories = ref(new Set())
const folders = ref([])
async function initFiles() {
const loadId = ++filesLoadId.value
const [filePaths, root] = await Promise.all([
get_pack_export_candidates(props.instance.id),
get_full_path(props.instance.id),
])
if (loadId !== filesLoadId.value) return
instanceRoot.value = root
const exportCandidates = await Promise.all(
filePaths.map((path) => buildExportCandidateItem(root, path)),
const initFiles = async () => {
const newFolders = new Map()
const sep = '/'
files.value = []
await get_pack_export_candidates(props.instance.id).then((filePaths) =>
filePaths
.map((folder) => ({
path: folder,
name: folder.split(sep).pop(),
selected:
folder.startsWith('mods') ||
folder.startsWith('datapacks') ||
folder.startsWith('resourcepacks') ||
folder.startsWith('shaderpacks') ||
folder.startsWith('config'),
disabled:
folder === 'profile.json' ||
folder.startsWith('modrinth_logs') ||
folder.startsWith('.fabric') ||
folder.startsWith('__MACOSX'),
}))
.forEach((pathData) => {
const parent = pathData.path.split(sep).slice(0, -1).join(sep)
if (parent !== '') {
if (newFolders.has(parent)) {
newFolders.get(parent).push(pathData)
} else {
newFolders.set(parent, [pathData])
}
} else {
files.value.push(pathData)
}
}),
)
if (loadId !== filesLoadId.value) return
files.value = exportCandidates
selectedFilePaths.value = files.value
.filter((file) => !file.disabled && isDefaultSelectedExportCandidate(file.path))
.map((file) => file.path)
folders.value = [...newFolders.entries()].map(([name, value]) => [
{
name,
showingMore: false,
},
value,
])
}
await initFiles()
const exportPack = async () => {
const filesToExport = files.value.filter((file) => file.selected).map((file) => file.path)
folders.value.forEach((args) => {
args[1].forEach((child) => {
if (child.selected) {
filesToExport.push(child.path)
}
})
})
const outputPath = await save({
defaultPath: `${nameInput.value} ${versionInput.value}.mrpack`,
filters: [
@@ -107,7 +139,7 @@ const exportPack = async () => {
export_instance_mrpack(
props.instance.id,
outputPath,
selectedFilePaths.value,
filesToExport,
versionInput.value,
exportDescription.value,
nameInput.value,
@@ -115,176 +147,94 @@ const exportPack = async () => {
exportModal.value.hide()
}
}
function resetExportState() {
nameInput.value = props.instance.name
exportDescription.value = ''
versionInput.value = '1.0.0'
files.value = []
selectedFilePaths.value = []
fileTreeKey.value += 1
instanceRoot.value = ''
loadedDirectories.value = new Set()
}
async function loadExportDirectory(path) {
if (!path || !instanceRoot.value || loadedDirectories.value.has(path)) return
const loadId = filesLoadId.value
loadedDirectories.value.add(path)
try {
const entries = await readDir(`${instanceRoot.value}/${path}`)
const childItems = await Promise.all(
entries.map((entry) => buildExportDirectoryChildItem(instanceRoot.value, path, entry)),
)
if (loadId !== filesLoadId.value) return
appendExportItems(childItems)
} catch {
loadedDirectories.value.delete(path)
}
}
async function buildExportCandidateItem(instanceRoot, path) {
try {
const entries = await readDir(`${instanceRoot}/${path}`)
const metadata = await getExportCandidateMetadata(instanceRoot, path)
return {
path,
type: 'directory',
disabled: isExportCandidateDisabled(path),
modified: metadata.modified,
count: entries.length,
}
} catch {
return buildExportFileItem(instanceRoot, path)
}
}
async function buildExportDirectoryChildItem(instanceRoot, parentPath, entry) {
const path = `${parentPath}/${entry.name}`
if (entry.isDirectory) {
const metadata = await getExportCandidateMetadata(instanceRoot, path)
return {
path,
type: 'directory',
disabled: isExportCandidateDisabled(path),
modified: metadata.modified,
}
}
return buildExportFileItem(instanceRoot, path)
}
async function buildExportFileItem(instanceRoot, path) {
const metadata = await getExportCandidateMetadata(instanceRoot, path)
return {
path,
type: 'file',
disabled: isExportCandidateDisabled(path),
size: metadata.size,
modified: metadata.modified,
}
}
function appendExportItems(items) {
const nextFiles = new Map(files.value.map((file) => [normalizeExportPath(file.path), file]))
for (const item of items) {
nextFiles.set(normalizeExportPath(item.path), item)
}
files.value = [...nextFiles.values()]
}
async function getExportCandidateMetadata(instanceRoot, path) {
try {
const metadata = await stat(`${instanceRoot}/${path}`)
return {
size: metadata.size,
modified: metadata.mtime ? Math.floor(metadata.mtime.getTime() / 1000) : undefined,
}
} catch {
return {}
}
}
function normalizeExportPath(path) {
return path.replaceAll('\\', '/').split('/').filter(Boolean).join('/')
}
function isDefaultSelectedExportCandidate(path) {
return (
path.startsWith('mods') ||
path.startsWith('datapacks') ||
path.startsWith('resourcepacks') ||
path.startsWith('shaderpacks') ||
path.startsWith('config')
)
}
function isExportCandidateDisabled(path) {
return (
path === 'profile.json' ||
path.startsWith('modrinth_logs') ||
path.startsWith('.fabric') ||
path.startsWith('__MACOSX')
)
}
</script>
<template>
<NewModal
ref="exportModal"
:header="formatMessage(messages.header)"
scrollable
width="46rem"
max-width="calc(100vw - 2rem)"
>
<div class="flex flex-col gap-4">
<ModalWrapper ref="exportModal" :header="formatMessage(messages.header)">
<div class="flex flex-col gap-4 w-[40rem]">
<div class="grid grid-cols-2 gap-4">
<div class="labeled_input w-full">
<p class="text-contrast font-semibold">{{ formatMessage(messages.modpackNameLabel) }}</p>
<div class="labeled_input">
<p>{{ formatMessage(messages.modpackNameLabel) }}</p>
<StyledInput
v-model="nameInput"
:icon="PackageIcon"
type="text"
:placeholder="formatMessage(messages.modpackNamePlaceholder)"
clearable
wrapper-class="w-full"
/>
</div>
<div class="labeled_input w-full">
<p class="text-contrast font-semibold">
{{ formatMessage(messages.versionNumberLabel) }}
</p>
<div class="labeled_input">
<p>{{ formatMessage(messages.versionNumberLabel) }}</p>
<StyledInput
v-model="versionInput"
:icon="VersionIcon"
type="text"
:placeholder="formatMessage(messages.versionNumberPlaceholder)"
clearable
wrapper-class="w-full"
/>
</div>
</div>
<div class="flex flex-col gap-2 min-w-0">
<p class="m-0 text-contrast font-semibold">
{{ formatMessage(commonMessages.descriptionLabel) }}
</p>
<div class="flex flex-col gap-2">
<p class="m-0">{{ formatMessage(commonMessages.descriptionLabel) }}</p>
<StyledInput
v-model="exportDescription"
multiline
:placeholder="formatMessage(messages.descriptionPlaceholder)"
wrapper-class="w-full"
/>
</div>
<FileTreeSelect
:key="fileTreeKey"
v-model="selectedFilePaths"
class="min-w-0"
:items="files"
@navigate="loadExportDirectory"
/>
</div>
<template #actions>
<Accordion
class="w-full bg-surface-4 border border-solid border-surface-5 rounded-2xl overflow-clip"
button-class="p-4 w-full border-b border-solid border-b-surface-5 bg-surface-2 -mb-px hover:brightness-[--hover-brightness] group"
>
<template #title>
<span class="flex items-center gap-3 text-contrast group-active:scale-[0.98]">
<WrenchIcon aria-hidden="true" class="size-5 text-secondary" />
Configure which files are included in this export
</span>
</template>
<div class="flex flex-col [&>*:nth-child(even)]:bg-surface-3">
<div v-for="[path, children] in folders" :key="path.name" class="flex flex-col">
<Accordion
class="flex flex-col"
button-class="flex gap-3 pr-4 hover:bg-surface-5 group"
>
<template #title>
<Checkbox
:model-value="children.every((child) => child.selected)"
:indeterminate="
!children.every((child) => child.selected) &&
children.some((child) => child.selected)
"
:description="formatMessage(messages.includeFile, { file: path.name })"
class="pl-4 py-2"
:disabled="children.every((x) => x.disabled)"
@update:model-value="
(newValue) => children.forEach((child) => (child.selected = newValue))
"
@click.stop
/>
<span class="ml-2 group-active:scale-95">{{ path.name }}/</span>
</template>
<div v-for="child in children" :key="child.path">
<Checkbox
v-model="child.selected"
:label="child.name"
class="w-full px-8 py-2 hover:bg-surface-4 text-primary"
:disabled="child.disabled"
/>
</div>
</Accordion>
</div>
<Checkbox
v-for="file in files"
:key="file.path"
v-model="file.selected"
:label="file.name"
:disabled="file.disabled"
class="w-full px-4 py-2 hover:bg-surface-4 text-primary"
/>
</div>
</Accordion>
<div class="flex items-center justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="exportModal.hide">
@@ -299,6 +249,6 @@ function isExportCandidateDisabled(path) {
</button>
</ButtonStyled>
</div>
</template>
</NewModal>
</div>
</ModalWrapper>
</template>
@@ -1,124 +0,0 @@
<template>
<ButtonStyled color="brand" type="outlined" hover-color-fill="background">
<button
v-if="showUpdatePill"
type="button"
class="!h-[34px] text-sm !transition-[opacity,transform,background-color,color,filter] !duration-200 ease-out"
:class="{
'opacity-0 scale-[0.96]': finishedDownloading && !animateReadyPill,
'opacity-100 scale-100': finishedDownloading && animateReadyPill,
}"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" :class="{ 'animate-spin': restarting }" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">{{ downloadPercent }}%</span>
</span>
<span v-else>{{ updateLabel }}</span>
</button>
</ButtonStyled>
</template>
<script setup lang="ts">
import { DownloadIcon, RefreshCwIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import {
appUpdateState,
downloadAvailableAppUpdate,
installAvailableAppUpdate,
} from '@/providers/app-update'
const { formatMessage } = useVIntl()
const messages = defineMessages({
update: {
id: 'app.action-bar.update',
defaultMessage: 'Update',
},
downloadingUpdate: {
id: 'app.action-bar.downloading-update',
defaultMessage: 'Downloading update',
},
reloadToUpdate: {
id: 'app.action-bar.reload-to-update',
defaultMessage: 'Reload to update',
},
})
const {
downloading,
downloadPercent,
downloadProgress,
finishedDownloading,
isVisible: isUpdateVisible,
metered,
restarting,
} = appUpdateState
const isUpdateDownloading = computed(
() =>
downloading.value ||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
)
const showUpdatePill = computed(
() => isUpdateVisible.value && (finishedDownloading.value || metered.value),
)
const animateReadyPill = ref(false)
const updateLabel = computed(() => {
if (isUpdateDownloading.value) {
return formatMessage(messages.downloadingUpdate)
}
if (finishedDownloading.value) {
return formatMessage(messages.reloadToUpdate)
}
return formatMessage(messages.update)
})
let readyPillAnimationFrame: number | null = null
watch([showUpdatePill, finishedDownloading], async ([show, ready], [wasShown, wasReady]) => {
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
readyPillAnimationFrame = null
}
if (!show || !ready) {
animateReadyPill.value = false
return
}
if (wasShown && wasReady) {
return
}
animateReadyPill.value = false
await nextTick()
readyPillAnimationFrame = requestAnimationFrame(() => {
animateReadyPill.value = true
readyPillAnimationFrame = null
})
})
async function handleUpdateClick() {
if (isUpdateDownloading.value) {
return
}
if (finishedDownloading.value) {
await installAvailableAppUpdate()
} else {
await downloadAvailableAppUpdate()
}
}
onBeforeUnmount(() => {
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
}
})
</script>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { MailIcon, SearchIcon, SendIcon, UserIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
import { MailIcon, SendIcon, UserIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
@@ -301,12 +301,11 @@ const messages = defineMessages({
</ButtonStyled>
<StyledInput
v-model="search"
:icon="SearchIcon"
type="text"
:placeholder="formatMessage(messages.searchFriends)"
clearable
input-class="!bg-transparent !border !border-solid !border-button-bg !text-primary !placeholder:text-primary"
wrapper-class="flex-1 [&>svg]:!text-primary [&>svg]:!opacity-100"
variant="outlined"
wrapper-class="flex-1"
@keyup.esc="search = ''"
/>
</template>
@@ -8,18 +8,13 @@ import type { ComputedRef, Ref } from 'vue'
import { onUnmounted, ref, shallowRef } from 'vue'
import type { Router } from 'vue-router'
import {
fetchCachedServerStatus,
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import { process_listener } from '@/helpers/events'
import { kill, list as listInstances } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_instance, getServerAddress } from '@/helpers/worlds'
import { add_server_to_instance, getServerAddress, getServerLatency } from '@/helpers/worlds'
interface BrowseServerInstance {
id: string
name: string
path: string
}
@@ -73,10 +68,12 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
const queryClient = useQueryClient()
const debugLog = useDebugLogger('BrowseServer')
const serverPings = shallowRef<Record<string, number | undefined>>({})
const serverPingCache = new Map<string, number | undefined>()
const pendingServerPings = new Map<string, Promise<number | undefined>>()
const runningServerProjects = ref<Record<string, string>>({})
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
const contextMenuRef = ref<ContextMenuHandle | null>(null)
let serverPingsActive = true
let serverPingCacheActive = true
let unlistenProcesses: (() => void) | null = null
async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
@@ -149,26 +146,37 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
})
const nextPings = { ...serverPings.value }
for (const { hit, address } of pingsToFetch) {
const cachedStatus = getFreshCachedServerStatus(queryClient, address)
if (cachedStatus) {
nextPings[hit.project_id] = cachedStatus.ping
if (serverPingCache.has(address)) {
nextPings[hit.project_id] = serverPingCache.get(address)
}
}
serverPings.value = nextPings
await Promise.all(
pingsToFetch.map(async ({ hit, address }) => {
if (getFreshCachedServerStatus(queryClient, address)) return
if (serverPingCache.has(address)) return
try {
const status = await fetchCachedServerStatus(queryClient, address)
if (!serverPingsActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: status.ping }
} catch (error) {
console.error(`Failed to ping server ${address}:`, error)
if (!serverPingsActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: undefined }
let pending = pendingServerPings.get(address)
if (!pending) {
pending = getServerLatency(address)
.then((latency) => {
if (serverPingCacheActive) serverPingCache.set(address, latency)
return latency
})
.catch((error) => {
console.error(`Failed to ping server ${address}:`, error)
if (serverPingCacheActive) serverPingCache.set(address, undefined)
return undefined
})
.finally(() => {
pendingServerPings.delete(address)
})
pendingServerPings.set(address, pending)
}
const latency = await pending
if (!serverPingCacheActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: latency }
}),
)
}
@@ -300,8 +308,10 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
.catch(options.handleError)
onUnmounted(() => {
serverPingsActive = false
serverPingCacheActive = false
unlistenProcesses?.()
serverPingCache.clear()
pendingServerPings.clear()
})
return {
@@ -1,50 +0,0 @@
import type { QueryClient } from '@tanstack/vue-query'
import {
get_server_status,
normalizeServerAddress,
type ProtocolVersion,
type ServerStatus,
} from '@/helpers/worlds'
export const SERVER_STATUS_CACHE_MS = 10 * 60 * 1000
function getProtocolVersionKey(protocolVersion: ProtocolVersion | null) {
if (!protocolVersion) return 'default'
return `${protocolVersion.version}:${protocolVersion.legacy ? 'legacy' : 'modern'}`
}
export function getServerStatusQueryKey(
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
return [
'minecraft-server-status',
normalizeServerAddress(address) || address.trim().toLowerCase(),
getProtocolVersionKey(protocolVersion),
] as const
}
export function getFreshCachedServerStatus(
queryClient: QueryClient,
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
const queryKey = getServerStatusQueryKey(address, protocolVersion)
const updatedAt = queryClient.getQueryState(queryKey)?.dataUpdatedAt ?? 0
if (!updatedAt || Date.now() - updatedAt >= SERVER_STATUS_CACHE_MS) return undefined
return queryClient.getQueryData<ServerStatus>(queryKey)
}
export async function fetchCachedServerStatus(
queryClient: QueryClient,
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
return await queryClient.fetchQuery({
queryKey: getServerStatusQueryKey(address, protocolVersion),
queryFn: () => get_server_status(address, protocolVersion),
staleTime: SERVER_STATUS_CACHE_MS,
gcTime: SERVER_STATUS_CACHE_MS,
})
}
+23 -30
View File
@@ -404,35 +404,28 @@ export async function refreshServerData(
): Promise<void> {
const refreshTime = Date.now()
serverData.refreshing = true
try {
const status = await get_server_status(address, protocolVersion)
if (serverData.lastSuccessfulRefresh && serverData.lastSuccessfulRefresh > refreshTime) {
// Don't update if there was a more recent successful refresh
return
}
serverData.lastSuccessfulRefresh = Date.now()
serverData.status = status
if (status.description) {
serverData.rawMotd = status.description
serverData.renderedMotd = autoToHTML(status.description)
} else {
delete serverData.rawMotd
delete serverData.renderedMotd
}
} catch (err) {
console.error(`Refreshing addr ${address}`, protocolVersion, err)
if (!protocolVersion?.legacy) {
await refreshServerData(serverData, { version: 74, legacy: true }, address)
return
}
if (!serverData.lastSuccessfulRefresh || serverData.lastSuccessfulRefresh <= refreshTime) {
delete serverData.status
delete serverData.rawMotd
delete serverData.renderedMotd
}
} finally {
serverData.refreshing = false
}
await get_server_status(address, protocolVersion)
.then((status) => {
if (serverData.lastSuccessfulRefresh && serverData.lastSuccessfulRefresh > refreshTime) {
// Don't update if there was a more recent successful refresh
return
}
serverData.lastSuccessfulRefresh = Date.now()
serverData.status = status
if (status.description) {
serverData.rawMotd = status.description
serverData.renderedMotd = autoToHTML(status.description)
}
})
.finally(() => {
serverData.refreshing = false
})
.catch((err) => {
console.error(`Refreshing addr ${address}`, protocolVersion, err)
if (!protocolVersion?.legacy) {
refreshServerData(serverData, { version: 74, legacy: true }, address)
}
})
}
export function refreshServers(
@@ -451,7 +444,7 @@ export function refreshServers(
}
})
// noinspection ES6MissingAwait - handled by refreshServerData
// noinspection ES6MissingAwait - handled with .then by refreshServerData already
Object.keys(serverData).forEach((address) =>
refreshServerData(serverData[address], protocolVersion, address),
)
@@ -122,6 +122,9 @@
"app.export-modal.modpack-name-placeholder": {
"message": "إسم حزمة التعديل"
},
"app.export-modal.select-files-label": {
"message": "أختيار الملفات التي سيتم تصديرها"
},
"app.export-modal.version-number-label": {
"message": "رقم الإصدار"
},
+22 -139
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Skrýt více spuštěných instancí"
},
"app.action-bar.install.dismiss": {
"message": "Zavřít"
},
"app.action-bar.install.failed": {
"message": "Instalace selhala"
},
"app.action-bar.install.failed-app-closed": {
"message": "Instalace selhala kvůli zavření aplikace."
},
"app.action-bar.install.failed-network": {
"message": "Instalace selhala kvůli chybě sítě."
},
"app.action-bar.install.failed-unknown": {
"message": "Instalace selhala kvůli neznámé chybě."
},
"app.action-bar.install.open-instance": {
"message": "Otevřít instanci"
},
"app.action-bar.install.retry": {
"message": "Zkusit znovu"
},
"app.action-bar.install.unknown-instance": {
"message": "Neznámá instance"
},
"app.action-bar.installs": {
"message": "Instalace"
},
"app.action-bar.make-primary-instance": {
"message": "Nastavit jako primární instanci"
},
@@ -60,7 +33,7 @@
"message": "Zastavit instanci"
},
"app.action-bar.update": {
"message": "Aktualizovat"
"message": "Aktualizace"
},
"app.action-bar.view-active-downloads": {
"message": "Zobrazit probíhající stahování"
@@ -128,14 +101,8 @@
"app.appearance-settings.show-play-time.title": {
"message": "Zobrazit odehraný čas"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Automaticky přeskočí nízko riziková potrvzení jako duplicitní instalace modpacků, běžné mazání obsahu, hromadné aktualizace, odpojování modpacků a výzvy k opravě. Nebezpečná upozornění se budou nadále zobrazovat."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Přeskočit nedůležitá upozornění"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Umožňí přepínat zobrazení postranního panelu."
"message": "Umožní přepínat zobrazení postranního panelu."
},
"app.appearance-settings.toggle-sidebar.title": {
"message": "Přepnout postranní panel"
@@ -174,10 +141,10 @@
"message": "Zpět k instanci"
},
"app.browse.discover-content": {
"message": "Objevit obsah"
"message": "Objevovat obsah"
},
"app.browse.discover-servers": {
"message": "Objevit servery"
"message": "Objevovat servery"
},
"app.browse.hide-added-servers": {
"message": "Skrýt již přidané servery"
@@ -192,7 +159,7 @@
"message": "Instalování"
},
"app.content-install.no-compatible-versions": {
"message": "Žádné dostupné verze se neshodují s {compatibilityLabel}. Vyberte verzi, kterou chcete přesto nainstalovat. Závislosti nebudou nainstalovány automaticky."
"message": "Žádná dostupná verze se neshoduje s verzí {compatibilityLabel}. Vyber verzi, abys ji i přesto nainstaloval/a. Závislosti nebudou automaticky nainstalovány."
},
"app.export-modal.description-placeholder": {
"message": "Zadej popis modpacku..."
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Exportovat modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Zahrnout \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Název modpacku"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Název modpacku"
},
"app.export-modal.select-files-label": {
"message": "Nastavte, které soubory mají být do tohoto exportu zahrnuty"
},
"app.export-modal.version-number-label": {
"message": "Číslo verze"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Stahování obsahu"
},
"app.install.phase.downloading_minecraft": {
"message": "Stahování Minecraftu"
},
"app.install.phase.downloading_pack_file": {
"message": "Stahování pack souboru"
},
"app.install.phase.extracting_overrides": {
"message": "Rozbalování souborů overrides"
},
"app.install.phase.finalizing": {
"message": "Dokončení instalace"
},
"app.install.phase.preparing_instance": {
"message": "Příprava instance"
},
"app.install.phase.preparing_java": {
"message": "Příprava Javy"
},
"app.install.phase.preparing_java.downloading": {
"message": "Stahování Javy {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Rozbalování Javy {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Získávání Javy {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Připravení Javy {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validace Javy {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Čtení pack manifestu"
},
"app.install.phase.resolving_loader": {
"message": "Vyhodnocování loaderu"
},
"app.install.phase.resolving_minecraft": {
"message": "Vyhodnocování Minecraftu"
},
"app.install.phase.resolving_pack": {
"message": "Vyhodnocování obsahu"
},
"app.install.phase.rolling_back": {
"message": "Obnovování do předchozího stavu"
},
"app.install.phase.running_loader_processors": {
"message": "Spuštění loader procesů"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Všechna data tvé instance budou trvale smazána, včetně světů, konfigurací a veškerého nainstalovaného obsahu."
},
@@ -276,7 +195,7 @@
"message": "Tato akce nemůže být vrácena"
},
"app.instance.confirm-delete.delete-button": {
"message": "Smazat instanci"
"message": "Odstranit instanci"
},
"app.instance.confirm-delete.header": {
"message": "Odstranění instance"
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Stahování {current, number}/{total, number} projektů..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Dokončení aktualizace..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Vyhodnocování verzí..."
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
@@ -326,20 +236,8 @@
"app.instance.worlds.browse-servers": {
"message": "Procházet servery"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Smazat svět"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Smazat svět"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Tento svět bude trvale smazán z této instance. Tuto akci nelze vrátit zpět."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Mazání {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Módované"
"message": "Módovaný"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
@@ -351,22 +249,10 @@
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Začněte přidáním serveru nebo procházením obsahu"
"message": "Přidej server nebo začni procházet"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nejsou přidané žádné servery nebo světy"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Odebrat server"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Odebrat server"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Tento server bude odebrán z vašeho seznamu serverů i ze seznamu serverů ve hře. Pokud znáte jeho adresu, můžete ho později znovu přidat."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Odebírání {name}"
"message": "Žádné servery nebo světy nebyly přidány"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hledat v {count} světech..."
@@ -453,7 +339,7 @@
"message": "Smazat skin"
},
"app.skins.delete-modal.description": {
"message": "Tímto dojde k trvalému smazání vybreného skinu. Tuto akci nelze vrátit zpět."
"message": "Tahle akce navždy smaže vybraný skin a nejde vrátit zpět."
},
"app.skins.delete-modal.title": {
"message": "Opravdu chceš smazat tenhle skin?"
@@ -582,19 +468,19 @@
"message": "Nadšený Modrinth Bot"
},
"app.skins.sign-in.title": {
"message": "Přihlaš se prosím"
"message": "Přihlas se prosím"
},
"app.skins.title": {
"message": "Výběr skinu"
},
"app.update-popup.body.download-complete": {
"message": "Stahování Modrinth App v{version} bylo dokončeno. Aktualizujte ji znovunačtením nebo se automaticky aktualizuje po zavření."
"message": "Stahování aplikace Modrinth v{version} bylo dokončeno. Aktualizujte ji znovunačtením nebo se automaticky aktualizuje po zavření."
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} je k dispozici. Aktualizujte ji pomocí svého správce balíčků, abyste získali nejnovější funkce a opravy!"
"message": "Aplikace Modrinth v{version} je k dispozici. Aktualizujte ji pomocí svého správce balíčků, abyste získali nejnovější funkce a opravy!"
},
"app.update-popup.body.metered": {
"message": "Modrinth App v{version} je nyní k dispozici! Protože jste v měřené síti, nebyla stažena automaticky."
"message": "Aplikace Modrinth v{version} je nyní k dispozici! Protože jste v měřené síti, nebyla stažena automaticky."
},
"app.update-popup.changelog": {
"message": "Seznam změn"
@@ -807,13 +693,13 @@
"message": "Jméno"
},
"instance.settings.tabs.general.update-channel": {
"message": "Kanál aktualiza"
"message": "Aktualizační kanál"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Verze typu release, beta a alpha se budou zobrazovat jako dostupné aktualizace."
"message": "Vydané, beta a alfa verze se zobrazí jako dostupné aktualizace."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "Kopírovat adresu"
},
"instance.worlds.create_shortcut": {
"message": "Vytvořit záložku"
},
"instance.worlds.dont_show_on_home": {
"message": "Nezobrazovat na domovské stránce"
},
@@ -164,12 +164,18 @@
"app.export-modal.header": {
"message": "Eksporter modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Inkludere \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpack Navn"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack navn"
},
"app.export-modal.select-files-label": {
"message": "Konfigurer hvilke filer inkludere i denne eksport"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Weitere laufende Instanzen ausblenden"
},
"app.action-bar.install.dismiss": {
"message": "Ignorieren"
},
"app.action-bar.install.failed": {
"message": "Installation fehlgeschlagen"
},
"app.action-bar.install.failed-app-closed": {
"message": "Installation aufgrund des Schließens der App fehlgeschlagen."
},
"app.action-bar.install.failed-network": {
"message": "Installation aufgrund eines Netzwerkfehlers fehlgeschlagen."
},
"app.action-bar.install.failed-unknown": {
"message": "Installation aufgrund eines unbekannten Fehlers fehlgeschlagen."
},
"app.action-bar.install.open-instance": {
"message": "Instanz öffnen"
},
"app.action-bar.install.retry": {
"message": "Wiederholen"
},
"app.action-bar.install.unknown-instance": {
"message": "Unbekannte Instanz"
},
"app.action-bar.installs": {
"message": "Installationen"
},
"app.action-bar.make-primary-instance": {
"message": "Zur primären Instanz machen"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Spielzeit anzeigen"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Überspringt automatisch Bestätigungen mit geringem Risiko, wie z. B. die doppelte Installation von Modpacks, das Löschen normaler Inhalte, Massenaktualisierungen, das Trennen von Modpacks und Reparaturen. Warnungen bei gefährlichen Aktionen werden weiterhin angezeigt."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Nicht essenzielle Warnungen überspringen"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Ermöglicht das Umschalten der Seitenleiste."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Modpack exportieren"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\" einschliessen?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpaketname"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpaketname"
},
"app.export-modal.select-files-label": {
"message": "Konfiguriere, welche Dateien in diesem Export miteinbezogen werden"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Lade Inhalte herunter"
},
"app.install.phase.downloading_minecraft": {
"message": "Lade Minecraft herunter"
},
"app.install.phase.downloading_pack_file": {
"message": "Lade Packdatei herunter"
},
"app.install.phase.extracting_overrides": {
"message": "Überschreibungen extrahieren"
},
"app.install.phase.finalizing": {
"message": "Abschliessen"
},
"app.install.phase.preparing_instance": {
"message": "Instanz präparieren"
},
"app.install.phase.preparing_java": {
"message": "Java präparieren"
},
"app.install.phase.preparing_java.downloading": {
"message": "Java {version} herunterladen"
},
"app.install.phase.preparing_java.extracting": {
"message": "Java {version} extrahieren"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Java {version} wird heruntergeladen"
},
"app.install.phase.preparing_java.resolving": {
"message": "Java {version} präparieren"
},
"app.install.phase.preparing_java.validating": {
"message": "Java {version} validieren"
},
"app.install.phase.reading_pack_manifest": {
"message": "Paketmanifest wird gelesen"
},
"app.install.phase.resolving_loader": {
"message": "Loader wird ermittelt"
},
"app.install.phase.resolving_minecraft": {
"message": "Minecraft wird ermittelt"
},
"app.install.phase.resolving_pack": {
"message": "Inhalte werden ermittelt"
},
"app.install.phase.rolling_back": {
"message": "Wird rückgängig gemacht"
},
"app.install.phase.running_loader_processors": {
"message": "Loader-Prozesse werden ausgeführt"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten deiner Instanz werden permanent gelöscht, inlusive deiner Welten, Konfigurationen und allen installierten Inhalten."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instanz"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Lade {current, number}/{total, number} Projekte..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Update abschliessen..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Versionen werden ermittelt..."
},
"app.instance.mods.content-type-project": {
"message": "Projekt"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "Server durchsuchen"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Diese Welt wird permanent gelöscht von dieser Instanz, dies kann nicht rückgängig gemacht werden."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Lösche {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modifiziert"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Dieser Server wird aus deiner Serverliste und aus der Serverliste im Spiel entfernt. Du kannst ihn später wieder hinzufügen, wenn du die Adresse kennst."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Entferne {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Durchsuche {count} Welten..."
},
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "Adresse kopieren"
},
"instance.worlds.create_shortcut": {
"message": "Shortcut erstellen"
},
"instance.worlds.dont_show_on_home": {
"message": "Nicht auf Startseite anzeigen"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Weitere laufende Instanzen ausblenden"
},
"app.action-bar.install.dismiss": {
"message": "Ausblenden"
},
"app.action-bar.install.failed": {
"message": "Installation fehlgeschlagen"
},
"app.action-bar.install.failed-app-closed": {
"message": "Installation aufgrund des Schließens der App fehlgeschlagen."
},
"app.action-bar.install.failed-network": {
"message": "Installation aufgrund eines Netzwerkfehlers fehlgeschlagen."
},
"app.action-bar.install.failed-unknown": {
"message": "Installation aufgrund eines unbekannten Fehlers fehlgeschlagen."
},
"app.action-bar.install.open-instance": {
"message": "Instanz öffnen"
},
"app.action-bar.install.retry": {
"message": "Erneut versuchen"
},
"app.action-bar.install.unknown-instance": {
"message": "Unbekannte Instanz"
},
"app.action-bar.installs": {
"message": "Installationen"
},
"app.action-bar.make-primary-instance": {
"message": "Zur primären Instanz machen"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Spielzeit anzeigen"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Überspringt automatisch Bestätigungen mit geringem Risiko, wie z. B. die doppelte Installation von Modpacks, das Löschen normaler Inhalte, Massenaktualisierungen, das Trennen von Modpacks und Reparaturen. Warnungen bei gefährlichen Aktionen werden weiterhin angezeigt."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Unwichtige Warnungen überspringen"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Ermöglicht das Umschalten der Seitenleiste."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Modpack exportieren"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\" einschließen?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpackname"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpackname"
},
"app.export-modal.select-files-label": {
"message": "Konfiguriere, welche Dateien in diesen Export enthalten sind"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Inhalte werden heruntergeladen"
},
"app.install.phase.downloading_minecraft": {
"message": "Minecraft wird heruntergeladen"
},
"app.install.phase.downloading_pack_file": {
"message": "Paketdatei wird herunterladen"
},
"app.install.phase.extracting_overrides": {
"message": "Überschreibungen werden extrahiert"
},
"app.install.phase.finalizing": {
"message": "Wird finalisiert"
},
"app.install.phase.preparing_instance": {
"message": "Instanz wird vorbereitet"
},
"app.install.phase.preparing_java": {
"message": "Java wird vorbereitet"
},
"app.install.phase.preparing_java.downloading": {
"message": "Java {version} wird heruntergeladen"
},
"app.install.phase.preparing_java.extracting": {
"message": "Java {version} wird extrahiert"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Java {version} wird heruntergeladen"
},
"app.install.phase.preparing_java.resolving": {
"message": "Java {version} wird vorbereitet"
},
"app.install.phase.preparing_java.validating": {
"message": "Java {version} wird validiert"
},
"app.install.phase.reading_pack_manifest": {
"message": "Paketmanifest wird gelesen"
},
"app.install.phase.resolving_loader": {
"message": "Loader wird ermittelt"
},
"app.install.phase.resolving_minecraft": {
"message": "Minecraft wird ermittelt"
},
"app.install.phase.resolving_pack": {
"message": "Inhalte werden ermittelt"
},
"app.install.phase.rolling_back": {
"message": "Wird rückgängig gemacht"
},
"app.install.phase.running_loader_processors": {
"message": "Loader-Prozesse werden ausgeführt"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten deiner Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instanz"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} Projekte werden heruntergelanden..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Update wird fertiggestellt..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Versionen werden ermittelt..."
},
"app.instance.mods.content-type-project": {
"message": "Projekt"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "Server durchsuchen"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Die Welt wird permanent von der Instanz gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "{name} wird gelöscht"
},
"app.instance.worlds.filter-modded": {
"message": "Modifiziert"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Dieser Server wird aus deiner Serverliste und aus der Serverliste im Spiel entfernt. Du kannst ihn später wieder hinzufügen, wenn du die Adresse kennst."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "{name} wird entfernt"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Durchsuche {count} Welten..."
},
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "Adresse kopieren"
},
"instance.worlds.create_shortcut": {
"message": "Verknüpfung erstellen"
},
"instance.worlds.dont_show_on_home": {
"message": "Nicht auf der Startseite anzeigen"
},
+8 -20
View File
@@ -180,7 +180,7 @@
"message": "Discover servers"
},
"app.browse.hide-added-servers": {
"message": "Hide servers already added"
"message": "Hide already added servers"
},
"app.browse.project-type.modpacks": {
"message": "Modpacks"
@@ -203,12 +203,18 @@
"app.export-modal.header": {
"message": "Export modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Include \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpack name"
"message": "Modpack Name"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack name"
},
"app.export-modal.select-files-label": {
"message": "Configure which files are included in this export"
},
"app.export-modal.version-number-label": {
"message": "Version number"
},
@@ -410,27 +416,12 @@
"app.project.install-button.already-installed": {
"message": "This project is already installed"
},
"app.project.install-button.switch-version": {
"message": "Switch version"
},
"app.project.install-context.back-to-browse": {
"message": "Back to discover"
},
"app.project.install-context.install-content-to-instance": {
"message": "Install content to instance"
},
"app.project.version.all-versions": {
"message": "All versions"
},
"app.project.version.download-in-browser": {
"message": "Download in browser"
},
"app.project.version.installing": {
"message": "Installing"
},
"app.project.versions.already-installed": {
"message": "Already installed"
},
"app.settings.developer-mode-enabled": {
"message": "Developer mode enabled."
},
@@ -551,9 +542,6 @@
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chaos-cubed": {
"message": "Chaos Cubed"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Ocultar más instancias en ejecución"
},
"app.action-bar.install.dismiss": {
"message": "Descartar"
},
"app.action-bar.install.failed": {
"message": "La instalación falló"
},
"app.action-bar.install.failed-app-closed": {
"message": "La instalación falló porque la aplicación se cerró."
},
"app.action-bar.install.failed-network": {
"message": "La instalación falló debido a un error de red."
},
"app.action-bar.install.failed-unknown": {
"message": "La instalación falló debido a un error desconocido."
},
"app.action-bar.install.open-instance": {
"message": "Abrir instancia"
},
"app.action-bar.install.retry": {
"message": "Reintentar"
},
"app.action-bar.install.unknown-instance": {
"message": "Instancia desconocida"
},
"app.action-bar.installs": {
"message": "Instalaciones"
},
"app.action-bar.make-primary-instance": {
"message": "Hacer instancia principal"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Mostrar tiempo de juego"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Omite automáticamente las confirmaciones de bajo riesgo, como las instalaciones duplicadas de paquetes de mods, la eliminación de contenido normal, las actualizaciones masivas, la desvinculación de paquetes de mods y las solicitudes de reparación. Las advertencias peligrosas seguirán mostrándose."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Omitir advertencias que no sean esenciales"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Permite mostrar u ocultar la barra lateral."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Exportar modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "¿Incluir \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nombre del modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nombre del modpack"
},
"app.export-modal.select-files-label": {
"message": "Configura que archivos incluir en esta exportación"
},
"app.export-modal.version-number-label": {
"message": "Número de la versión"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Descargando contenido"
},
"app.install.phase.downloading_minecraft": {
"message": "Descargando Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Descargando el archivo del paquete"
},
"app.install.phase.extracting_overrides": {
"message": "Extracción de anulaciones"
},
"app.install.phase.finalizing": {
"message": "Finalizando"
},
"app.install.phase.preparing_instance": {
"message": "Preparando la instancia"
},
"app.install.phase.preparing_java": {
"message": "Preparando Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Descargando Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Descomprimiendo Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Obteniendo Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparando Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validación de Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Leer el manifiesto del paquete"
},
"app.install.phase.resolving_loader": {
"message": "Resolviendo el cargador"
},
"app.install.phase.resolving_minecraft": {
"message": "Resolviendo Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Resolviendo el contenido"
},
"app.install.phase.rolling_back": {
"message": "Revertir"
},
"app.install.phase.running_loader_processors": {
"message": "Ejecutando los procesadores del cargador"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instancia"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Descargando {current, number}/{total, number} proyectos..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalizando la actualización..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Resolviendo versiones..."
},
"app.instance.mods.content-type-project": {
"message": "proyecto"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Eliminar mundo"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Eliminar mundo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Este mundo se eliminará de forma permanente de esta instancia. Esta acción no se puede deshacer."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Eliminando {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Con mods"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "No hay servidores ni mundos añadidos"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Eliminar servidor"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Eliminar servidor"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Este servidor se eliminará de tu lista de servidores y de la lista de servidores del juego. Puedes volver a agregarlo más adelante si conoces la dirección."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Se está eliminando {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar en {count} mundos..."
},
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "Copiar dirección"
},
"instance.worlds.create_shortcut": {
"message": "Crear un acceso directo"
},
"instance.worlds.dont_show_on_home": {
"message": "No mostrar en la página de inicio"
},
+6 -12
View File
@@ -11,18 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Ocultar más instancias en ejecución"
},
"app.action-bar.install.dismiss": {
"message": "Descartar"
},
"app.action-bar.install.failed": {
"message": "La instalación falló"
},
"app.action-bar.install.failed-app-closed": {
"message": "La instalación falló porque la aplicación se cerró."
},
"app.action-bar.install.failed-network": {
"message": "La instalación falló debido a un error de red."
},
"app.action-bar.make-primary-instance": {
"message": "Establecer como instancia principal"
},
@@ -182,12 +170,18 @@
"app.export-modal.header": {
"message": "Exportar modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "¿Incluir \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nombre del modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nombre del modpack"
},
"app.export-modal.select-files-label": {
"message": "Configura que archivos se incluirán en esta instancia"
},
"app.export-modal.version-number-label": {
"message": "Número de versión"
},
+6 -285
View File
@@ -2,42 +2,12 @@
"app.action-bar.downloading-java": {
"message": "Ladataan Java-versiota {version}"
},
"app.action-bar.downloading-update": {
"message": "Ladataan päivitystä"
},
"app.action-bar.downloads": {
"message": "Lataukset"
},
"app.action-bar.hide-more-running-instances": {
"message": "Piilota enemmän käynnissä olevia instansseja"
},
"app.action-bar.install.dismiss": {
"message": "Hylkää"
},
"app.action-bar.install.failed": {
"message": "Asennus epäonnistui"
},
"app.action-bar.install.failed-app-closed": {
"message": "Asennus epäonnistui sovelluksen sulkeutumisen vuoksi."
},
"app.action-bar.install.failed-network": {
"message": "Asennus epäonnistui verkkovirheen vuoksi."
},
"app.action-bar.install.failed-unknown": {
"message": "Asennus epäonnistui tuntemattoman virheen vuoksi."
},
"app.action-bar.install.open-instance": {
"message": "Avaa instanssi"
},
"app.action-bar.install.retry": {
"message": "Yritä uudelleen"
},
"app.action-bar.install.unknown-instance": {
"message": "Tuntematon instanssi"
},
"app.action-bar.installs": {
"message": "Asennukset"
},
"app.action-bar.make-primary-instance": {
"message": "Aseta ensisijaiseksi instanssiksi"
},
@@ -50,18 +20,12 @@
"app.action-bar.primary-instance": {
"message": "Ensisijainen instanssi"
},
"app.action-bar.reload-to-update": {
"message": "Lataa uudelleen päivittääksesi"
},
"app.action-bar.show-more-running-instances": {
"message": "Näytä enemmän käynnissä olevia instansseja"
},
"app.action-bar.stop-instance": {
"message": "Pysäytä instanssi"
},
"app.action-bar.update": {
"message": "Päivitä"
},
"app.action-bar.view-active-downloads": {
"message": "Näytä aktiiviset lataukset"
},
@@ -122,18 +86,6 @@
"app.appearance-settings.select-option": {
"message": "Valitse vaihtoehto"
},
"app.appearance-settings.show-play-time.description": {
"message": "Näyttää, kuinka paljon aikaa olet käyttänyt instanssin pelaamiseen."
},
"app.appearance-settings.show-play-time.title": {
"message": "Näyttää peliaika"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Ohittaa automaattisesti vähäriskiset vahvistukset, kuten modpackien kaksoisasennukset, tavallisen sisällön poistamisen, massapäivitykset, modpackien linkityksen poistamisen ja korjauskehotteet. Vaaralliset varoitukset näytetään silti."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Ohita ei olennaiset varoitukset"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Laittaa päälle mahdollisuuden kytkeä sivupalkkia."
},
@@ -203,72 +155,24 @@
"app.export-modal.header": {
"message": "Vie modipaketti"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Sisällytä \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modipaketin nimi"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modipaketin nimi"
},
"app.export-modal.select-files-label": {
"message": "Muuta mitkä tiedostot sisältyvät tähän vientiin"
},
"app.export-modal.version-number-label": {
"message": "Versio numero"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Ladataan sisältöä"
},
"app.install.phase.downloading_minecraft": {
"message": "Ladataan Minecraftia"
},
"app.install.phase.downloading_pack_file": {
"message": "Ladataan paketti tiedostoja"
},
"app.install.phase.extracting_overrides": {
"message": "Puretaan ohituksia"
},
"app.install.phase.finalizing": {
"message": "Viimeistellään"
},
"app.install.phase.preparing_instance": {
"message": "Valmistellaan instanssia"
},
"app.install.phase.preparing_java": {
"message": "Valmistellaan Javaa"
},
"app.install.phase.preparing_java.downloading": {
"message": "Ladataan Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Puretaan Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Haetaan Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Valmistellaan Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Tarkistetaan Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Luetaan paketin manifestia"
},
"app.install.phase.resolving_loader": {
"message": "Ratkaistaan lataajaa"
},
"app.install.phase.resolving_minecraft": {
"message": "Ratkaistaan Minecraftia"
},
"app.install.phase.resolving_pack": {
"message": "Ratkaistaan sisältöä"
},
"app.install.phase.rolling_back": {
"message": "Palautetaan muutoksia"
},
"app.install.phase.running_loader_processors": {
"message": "Suoritetaan lataajan käsittelijöitä"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Kaikki sinun instanssisi data poistetaan pysyvästi, sisältäen maailmasi, konfiguraatiosi ja asennetun sisällön."
},
@@ -293,15 +197,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instanssi"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Ladataan {current, number}/{total, number} projekteja..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Viimeistellään päivitystä..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Selvitetään versioita..."
},
"app.instance.mods.content-type-project": {
"message": "projekti"
},
@@ -326,18 +221,6 @@
"app.instance.worlds.browse-servers": {
"message": "Selaa palvelimia"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Poista maailma"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Poista maailma"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Tämä maailma poistetaan pysyvästi tästä instanssista. Tätä toimintoa ei voi kumota."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Poistetaan {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modattu"
},
@@ -356,18 +239,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Ei palvelimia tai maailmoja lisättynä"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Poista palvelin"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Poista palvelin"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Tämä palvelin poistetaan palvelinluettelostasi ja pelin sisäisestä palvelinluettelosta. Voit lisätä sen myöhemmin uudelleen, jos tiedät osoitteen."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Poistetaan {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hae {count} maailmasta..."
},
@@ -440,147 +311,15 @@
"app.settings.tabs.resource-management": {
"message": "Resurssien hallinta"
},
"app.skins.add-button": {
"message": "Lisää ulkoasu"
},
"app.skins.add-button.drag-and-drop": {
"message": "Raahaa ja pudota"
},
"app.skins.apply-button": {
"message": "Käytä"
},
"app.skins.delete-button": {
"message": "Poista ulkoasu"
},
"app.skins.delete-modal.description": {
"message": "Tämä poistaa valitun skinin pysyvästi. Tätä toimintoa ei voi kumota."
},
"app.skins.delete-modal.title": {
"message": "Haluatko varmasti poistaa ulkoasun?"
},
"app.skins.dropped-file-error.text": {
"message": "Tiedoston lukeminen epäonnistui."
},
"app.skins.dropped-file-error.title": {
"message": "Virhe prosessoidessa tiedostoa"
},
"app.skins.edit-button": {
"message": "Muokkaa ulkoasua"
},
"app.skins.modal.add-skin-button": {
"message": "Lisää ulkoasu"
},
"app.skins.modal.add-title": {
"message": "Lisätään ulkoasua"
},
"app.skins.modal.arm-style-section": {
"message": "Käsien tyyli"
},
"app.skins.modal.arm-style-slim": {
"message": "Ohut"
},
"app.skins.modal.arm-style-wide": {
"message": "Paksu"
},
"app.skins.modal.cape-fallback-name": {
"message": "Viitta"
},
"app.skins.modal.cape-section": {
"message": "Viitta"
},
"app.skins.modal.edit-title": {
"message": "Muokataan ulkoasua"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "Tee muokkaus ulkoasuun ensin!"
},
"app.skins.modal.no-cape-tooltip": {
"message": "Ei viittaa"
},
"app.skins.modal.none-cape-option": {
"message": "Ei mitään"
},
"app.skins.modal.replace-texture-button": {
"message": "Korvaa tekstuuri"
},
"app.skins.modal.save-skin-button": {
"message": "Tallenna ulkoasu"
},
"app.skins.modal.saving-tooltip": {
"message": "Tallennetaan..."
},
"app.skins.modal.texture-section": {
"message": "Tekstuuri"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "Lataa ulkoasu ensin!"
},
"app.skins.preview.edit-button": {
"message": "Muokkaa ulkoasua"
},
"app.skins.previewing-badge": {
"message": "Esikatsellaan"
},
"app.skins.rate-limit.text": {
"message": "Vaihdat ulkoasua liian usein. Mojangin palvelimet ovat estäneet uudet pyynnöt väliaikaisesti. Yritä hetken kuluttua uudelleen."
},
"app.skins.rate-limit.title": {
"message": "Hidasta vähän!"
},
"app.skins.reorder-error.text": {
"message": "Ulkoasujen järjestystä ei voitu tallentaa."
},
"app.skins.reorder-error.title": {
"message": "Skinien järjestyksen muuttaminen epäonnistui"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
"app.skins.section.default-skins": {
"message": "Oletus ulkoasut"
},
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
"app.skins.section.modrinth-pride.tooltip": {
"message": "Sait nämä skinit lahjoittamalla Modrinthin Pride-keräykseen Pride-kuukauden aikana."
},
"app.skins.section.mounts-of-mayhem": {
"message": "Sekasorron ratsut"
},
"app.skins.section.saved-skins": {
"message": "Tallennetut ulkoasut"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "The Copper Age"
},
"app.skins.section.the-garden-awakens": {
"message": "The Garden Awakens"
},
"app.skins.section.tiny-takeover": {
"message": "Pienten vallankumous"
},
"app.skins.sign-in.button": {
"message": "Kirjaudu sisään"
},
"app.skins.sign-in.description": {
"message": "Kirjaudu Minecraft-tilillesi käyttääksesi Modrinth-sovelluksen ulkoasujen hallintaominaisuuksia."
},
"app.skins.sign-in.rinthbot-alt": {
"message": "Innostunut Modrinth Botti"
},
"app.skins.sign-in.title": {
"message": "Kirjaudu sisään"
},
@@ -806,30 +545,15 @@
"instance.settings.tabs.general.name": {
"message": "Nimi"
},
"instance.settings.tabs.general.update-channel": {
"message": "Päivitys kanava"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Release-, beta- ja alpha-versiot näytetään saatavilla olevina päivityksinä."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Release- ja beta-versiot näytetään saatavilla olevina päivityksinä."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Julkaisu"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Vain release-versiot näytetään saatavilla olevina päivityksinä."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Valitse päivitys kanava"
},
"instance.settings.tabs.hooks": {
"message": "Aloitustoiminnot"
},
@@ -953,9 +677,6 @@
"instance.worlds.copy_address": {
"message": "Kopioi osoite"
},
"instance.worlds.create_shortcut": {
"message": "Luo pikakuvake"
},
"instance.worlds.dont_show_on_home": {
"message": "Älä näytä koti näkymässä"
},
@@ -164,12 +164,18 @@
"app.export-modal.header": {
"message": "Iluwas ang modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Salihin ang \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Pangalan ng Modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Pangalan ng modpack"
},
"app.export-modal.select-files-label": {
"message": "Isaayos kung anong mga file ang isasali sa pagluwas"
},
"app.export-modal.version-number-label": {
"message": "Numero ng bersiyon"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Masquer plus d'instances en exécution"
},
"app.action-bar.install.dismiss": {
"message": "Rejeter"
},
"app.action-bar.install.failed": {
"message": "Échec de l'installation"
},
"app.action-bar.install.failed-app-closed": {
"message": "Échec de l'installation dû à la fermeture de l'app."
},
"app.action-bar.install.failed-network": {
"message": "Échec de l'installation dû à une erreur réseau."
},
"app.action-bar.install.failed-unknown": {
"message": "Échec de l'installation dû à une erreur inconnue."
},
"app.action-bar.install.open-instance": {
"message": "Ouvrir l'instance"
},
"app.action-bar.install.retry": {
"message": "Réessayer"
},
"app.action-bar.install.unknown-instance": {
"message": "Instance inconnue"
},
"app.action-bar.installs": {
"message": "Installations"
},
"app.action-bar.make-primary-instance": {
"message": "Définir en tante qu'instance première"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Afficher le temps de jeu"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Ignore automatiquement les confirmations à faible risque, comme l'installation de modpacks en double, la suppression de contenu standard, les mises à jour groupées, la dissociation des modpacks et les demandes de réparation. Les avertissements concernant les actions dangereuses continueront d'être affichés."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Cacher les alertes non essentielles"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Permet d'ouvrir ou de fermer la barre latérale."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Exporter le modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Inclure « {file} » ?"
},
"app.export-modal.modpack-name-label": {
"message": "Nom du modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nom du modpack"
},
"app.export-modal.select-files-label": {
"message": "Configurez quels fichiers sont inclus dans cette exportation"
},
"app.export-modal.version-number-label": {
"message": "Numéro de version"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Téléchargement du contenu"
},
"app.install.phase.downloading_minecraft": {
"message": "Téléchargement de Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Téléchargement du fichier de pack"
},
"app.install.phase.extracting_overrides": {
"message": "Extraction des fichiers personnalisés"
},
"app.install.phase.finalizing": {
"message": "Finalisation"
},
"app.install.phase.preparing_instance": {
"message": "Préparation de l'instance"
},
"app.install.phase.preparing_java": {
"message": "Préparation de Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Téléchargement de Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extraction de Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Récupération de Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Préparation de Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validation de Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Lecture du manifeste du pack"
},
"app.install.phase.resolving_loader": {
"message": "Résolution du loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Résolution de Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Résolution du contenu"
},
"app.install.phase.rolling_back": {
"message": "Annulation"
},
"app.install.phase.running_loader_processors": {
"message": "Exécution des processeurs du chargeur"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Toutes les données pour votre instance seront supprimées à jamais, y compris vos mondes, vos configurations, et le contenu installé."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Téléchargement de {current, number}/{total, number} projets..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalisation de la mise à jour..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Résolution des versions..."
},
"app.instance.mods.content-type-project": {
"message": "projet"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "Parcourir des serveurs"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Supprimer le monde"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Supprimer le monde"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Ce monde sera définitivement supprimé de cette instance. Cette action est irréversible."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Suppression de {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Moddé"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Aucun serveur ou monde ajoutés"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Enlever le serveur"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Enlever le serveur"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Ce serveur sera enlevé de votre liste de serveur et de la liste de serveur en jeu. Vous pourrez le rajouter plus tard si vous connaissez son adresse."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Retrait de {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Rechercher {count} mondes..."
},
@@ -950,9 +836,6 @@
"instance.worlds.copy_address": {
"message": "Copier l'adresse"
},
"instance.worlds.create_shortcut": {
"message": "Créer un raccourci"
},
"instance.worlds.dont_show_on_home": {
"message": "Ne pas montrer dans l'Accueil"
},
@@ -11,12 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "További futó példányok elrejtése"
},
"app.action-bar.install.retry": {
"message": "Újra"
},
"app.action-bar.install.unknown-instance": {
"message": "Ismeretlen profil"
},
"app.action-bar.make-primary-instance": {
"message": "Beállítás elsődleges példányként"
},
@@ -176,21 +170,24 @@
"app.export-modal.header": {
"message": "Modcsomag exportálása"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Tartalmaz „{file}” fájlokat?"
},
"app.export-modal.modpack-name-label": {
"message": "A modcsomag neve"
},
"app.export-modal.modpack-name-placeholder": {
"message": "A modcsomag neve"
},
"app.export-modal.select-files-label": {
"message": "Állítsd be, hogy mely fájlok kerüljenek bele az exportba"
},
"app.export-modal.version-number-label": {
"message": "Verziószám"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.resolving_loader": {
"message": ""
},
"app.instance.confirm-delete.admonition-body": {
"message": "Az profilodhoz tartozó összes adat véglegesen törlődik, beleértve a világokat, a beállításokat és az összes telepített tartalmat."
},
@@ -170,12 +170,18 @@
"app.export-modal.header": {
"message": "Ekspor paket mod"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Sertakan \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nama Paket Mod"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nama paket mod"
},
"app.export-modal.select-files-label": {
"message": "Konfigurasikan berkas mana yang disertakan dalam pengeksporan ini"
},
"app.export-modal.version-number-label": {
"message": "Nomor versi"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Mostra meno istanze in esecuzione"
},
"app.action-bar.install.dismiss": {
"message": "Ignora"
},
"app.action-bar.install.failed": {
"message": "Installazione fallita"
},
"app.action-bar.install.failed-app-closed": {
"message": "Installazione fallita: l'app è stata chiusa."
},
"app.action-bar.install.failed-network": {
"message": "Installazione fallita: errore di rete."
},
"app.action-bar.install.failed-unknown": {
"message": "Installazione fallita: errore sconosciuto."
},
"app.action-bar.install.open-instance": {
"message": "Apri istanza"
},
"app.action-bar.install.retry": {
"message": "Riprova"
},
"app.action-bar.install.unknown-instance": {
"message": "Istanza sconosciuta"
},
"app.action-bar.installs": {
"message": "Installazioni"
},
"app.action-bar.make-primary-instance": {
"message": "Rendi primaria"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Mostra le ore di gioco"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Salta automaticamente le conferme a basso rischio, come l'installazione di pacchetti di mod duplicati, la normale eliminazione di contenuti, gli aggiornamenti simultanei, lo scollegare i pacchetti di mod e le richieste di riparazione. Le avvertenze verranno comunque mostrate."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Salta le conferme poco importanti"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Mostra o nascondi la barra laterale."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Esporta pacchetto"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Includere \"{file}\" nell'esportazione?"
},
"app.export-modal.modpack-name-label": {
"message": "Nome del pacchetto"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nome del pacchetto"
},
"app.export-modal.select-files-label": {
"message": "Seleziona i file da includere in questa esportazione"
},
"app.export-modal.version-number-label": {
"message": "Numero di versione"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Download dei contenuti"
},
"app.install.phase.downloading_minecraft": {
"message": "Download di Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Download del pacchetto"
},
"app.install.phase.extracting_overrides": {
"message": "Estrazione degli override"
},
"app.install.phase.finalizing": {
"message": "Finalizzazione"
},
"app.install.phase.preparing_instance": {
"message": "Preparazione dell'istanza"
},
"app.install.phase.preparing_java": {
"message": "Preparazione di Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Download di Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Estrazione di Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Recupero di Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparazione di Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validazione di Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Lettura del manifesto"
},
"app.install.phase.resolving_loader": {
"message": "Risoluzione del loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Risoluzione di Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Risoluzione dei contenuti"
},
"app.install.phase.rolling_back": {
"message": "Tornando indietro"
},
"app.install.phase.running_loader_processors": {
"message": "Esecuzione dei processori del loader"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Tutti i dati della tua istanza verranno eliminati permanentemente, inclusi i tuoi mondi, configurazioni e tutti i contenuti installati."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Istanza"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} progetti scaricati..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalizzando l'aggiornamento..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Risolvendo le versioni..."
},
"app.instance.mods.content-type-project": {
"message": "progetto"
},
@@ -323,18 +233,6 @@
"app.instance.worlds.browse-servers": {
"message": "Esplora i server"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Elimina mondo"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Eliminare il mondo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Questo mondo verrà eliminato permanentemente da questa istanza. Questa azione non può essere annullata."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Eliminare {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Moddato"
},
@@ -353,18 +251,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Nessun server o mondo aggiunto"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Rimuovi server"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Rimuovere il server"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Questo server verrà rimosso dalla tua lista di server nell'app e nel gioco. Potrai aggiungerlo nuovamente se conosci l'indirizzo."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Rimuovere {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cerca tra {count} mondi..."
},
@@ -950,9 +836,6 @@
"instance.worlds.copy_address": {
"message": "Copia indirizzo"
},
"instance.worlds.create_shortcut": {
"message": "Crea collegamento"
},
"instance.worlds.dont_show_on_home": {
"message": "Non mostrare nella home"
},
@@ -306,7 +306,7 @@
"message": "マントなし"
},
"app.skins.modal.replace-texture-button": {
"message": "アップロード"
"message": "置きかえる"
},
"app.skins.modal.texture-section": {
"message": "テクスチャ"
@@ -164,12 +164,18 @@
"app.export-modal.header": {
"message": "모드팩 내보내기"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\"(을)를 포함할까요?"
},
"app.export-modal.modpack-name-label": {
"message": "모드팩 이름"
},
"app.export-modal.modpack-name-placeholder": {
"message": "모드팩 이름"
},
"app.export-modal.select-files-label": {
"message": "내보내기에 어느 파일이 포함될지 구성"
},
"app.export-modal.version-number-label": {
"message": "버전 구분"
},
@@ -155,12 +155,18 @@
"app.export-modal.header": {
"message": "Eksport pek mod"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Sertakan \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nama Pek Mod"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nama pek mod"
},
"app.export-modal.select-files-label": {
"message": "Konfigurasikan fail yang disertakan dalam eksport ini"
},
"app.export-modal.version-number-label": {
"message": "Nombor versi"
},
@@ -33,7 +33,7 @@
"message": "Stop instantie"
},
"app.action-bar.update": {
"message": "Bijwerken"
"message": "Updaten"
},
"app.action-bar.view-active-downloads": {
"message": "Bekijk actieve downloads"
@@ -60,7 +60,7 @@
"message": "Wijzig de pagina waarop de launcher opent."
},
"app.appearance-settings.default-landing-page.home": {
"message": "Start"
"message": "Home"
},
"app.appearance-settings.default-landing-page.library": {
"message": "Bibliotheek"
@@ -170,12 +170,18 @@
"app.export-modal.header": {
"message": "Exporteer modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Betrek \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpack Naam"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack naam"
},
"app.export-modal.select-files-label": {
"message": "Configureer welke bestanden bij deze export zijn betrokken"
},
"app.export-modal.version-number-label": {
"message": "Versie nummer"
},
+8 -125
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Ukryj włączone instancje"
},
"app.action-bar.install.dismiss": {
"message": "Odrzuć"
},
"app.action-bar.install.failed": {
"message": "Instalacja nie powiodła się"
},
"app.action-bar.install.failed-app-closed": {
"message": "Instalacja nie powiodła się z powodu zamknięcia aplikacji."
},
"app.action-bar.install.failed-network": {
"message": "Instalacja nie powiodła się z powodu problemów z połączeniem."
},
"app.action-bar.install.failed-unknown": {
"message": "Instalacja nie powiodła się z nieznanych przyczyn."
},
"app.action-bar.install.open-instance": {
"message": "Otwórz instancję"
},
"app.action-bar.install.retry": {
"message": "Spróbuj ponownie"
},
"app.action-bar.install.unknown-instance": {
"message": "Nieznana instancja"
},
"app.action-bar.installs": {
"message": "Instalacje"
},
"app.action-bar.make-primary-instance": {
"message": "Ustaw jako główną instancję"
},
@@ -78,7 +51,7 @@
"message": "Zaawansowane renderowanie"
},
"app.appearance-settings.color-theme.description": {
"message": "Wybierz preferowany motyw w Modrinth app."
"message": "Wybierz preferowany motyw w Modrinth App."
},
"app.appearance-settings.color-theme.title": {
"message": "Motyw"
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Pokaż czas gry"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Automatycznie pomija mało znaczące potwierdzenia takie jak duplikaty instalacji paczek modów, usuwanie zwykłej zawartości, masowe aktualizacje, odłączanie paczek modów i naprawianie. Ostrzeżenia o potencjalnie niebezpiecznych akcjach będą nadal pokazywane."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Pomiń mało znaczące ostrzeżenia"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Umożliwia przełączenie paska bocznego."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Eksportuj paczkę modów"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Dołączyć \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nazwa paczki modów"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nazwa paczki modów"
},
"app.export-modal.select-files-label": {
"message": "Skonfiguruje jakie pliki zostaną załączone w tym eksporcie"
},
"app.export-modal.version-number-label": {
"message": "Numer wersji"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Pobieranie zawartości"
},
"app.install.phase.downloading_minecraft": {
"message": "Pobieranie Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Pobieranie pliku paczki"
},
"app.install.phase.extracting_overrides": {
"message": "Wyodrębnianie nadpisań"
},
"app.install.phase.finalizing": {
"message": "Finalizowanie"
},
"app.install.phase.preparing_instance": {
"message": "Przygotowywanie instancji"
},
"app.install.phase.preparing_java": {
"message": "Przygotowywanie środowiska Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Pobieranie Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Wypakowywanie Javy {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Pobieranie Javy {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Przygotowywanie Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Sprawdzanie Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Ustalanie manifestu paczki"
},
"app.install.phase.resolving_loader": {
"message": "Ustalanie programu ładującego"
},
"app.install.phase.resolving_minecraft": {
"message": "Ustalanie wersji Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Ustalanie treści"
},
"app.install.phase.rolling_back": {
"message": "Przywracanie"
},
"app.install.phase.running_loader_processors": {
"message": "Uruchamianie procesorów programu ładującego"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Wszystkie dane z Twojej instancji zostaną trwale usunięte, w tym Twoje światy, pliki konfiguracji i jakakolwiek dodana zawartość."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instalacja"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Pobieranie {current, number}/{total, number} projektów..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Kończenie aktualizacji..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Sprawdzanie wersji..."
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
@@ -323,18 +233,6 @@
"app.instance.worlds.browse-servers": {
"message": "Szukaj serwerów"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Usuń świat"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Usuń świat"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Ten świat zostanie trwale usunięty z tej instancji. Tej akcji nie będzie można cofnąć."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Usuwanie {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Zmodowany"
},
@@ -353,18 +251,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Nie dodano żadnych światów ani serwerów"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Usuń serwer"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Usuń serwer"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Ten serwer zostanie trwale usunięty z tej instancji. Tej akcji nie będzie można cofnąć."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Usuwanie {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Szukaj wśród {count} światów..."
},
@@ -453,7 +339,7 @@
"message": "To trwale usunie wybraną skórkę. Tej akcji nie można odwrócić."
},
"app.skins.delete-modal.title": {
"message": "Czy na pewno chcesz usunąć tę skórkę?"
"message": "Czy na pewno chcesz usunąć tą skórkę?"
},
"app.skins.dropped-file-error.text": {
"message": "Nie udało się odczytać wybranego pliku."
@@ -950,9 +836,6 @@
"instance.worlds.copy_address": {
"message": "Kopiuj adres"
},
"instance.worlds.create_shortcut": {
"message": "Utwórz skrót"
},
"instance.worlds.dont_show_on_home": {
"message": "Nie pokazuj na stronie głównej"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Ocultar mais instâncias em execução"
},
"app.action-bar.install.dismiss": {
"message": "Dispensar"
},
"app.action-bar.install.failed": {
"message": "Instalação falha"
},
"app.action-bar.install.failed-app-closed": {
"message": "A instalação falhou devido ao aplicativo ter fechado."
},
"app.action-bar.install.failed-network": {
"message": "A instalação falhou devido a um erro na rede."
},
"app.action-bar.install.failed-unknown": {
"message": "A instalação falhou devido a um erro desconhecido."
},
"app.action-bar.install.open-instance": {
"message": "Abrir instância"
},
"app.action-bar.install.retry": {
"message": "Tentar novamente"
},
"app.action-bar.install.unknown-instance": {
"message": "Instância desconhecida"
},
"app.action-bar.installs": {
"message": "Instalações"
},
"app.action-bar.make-primary-instance": {
"message": "Tornar instância principal"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Exibir tempo jogado"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Pula automaticamente as confirmações desnecessárias como duplica de instalação de pacotes de mods, exclusão de conteúdo normal, atualizações em massa, desvinculo de pacotes de mods e diálogos de reparo. Os avisos de perigo ainda serão exibidos."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Pular avisos desnecessários"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Permite alternar a barra lateral."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Exportar pacote de mods"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Incluir \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nome do pacote de mods"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nome do pacote de mods"
},
"app.export-modal.select-files-label": {
"message": "Configure quais arquivos serão incluídos na exportação"
},
"app.export-modal.version-number-label": {
"message": "Número da versão"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Instalando conteúdo"
},
"app.install.phase.downloading_minecraft": {
"message": "Instalando Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Instalando arquivo de pacote"
},
"app.install.phase.extracting_overrides": {
"message": "Extraindo substitutos"
},
"app.install.phase.finalizing": {
"message": "Finalizando"
},
"app.install.phase.preparing_instance": {
"message": "Preparando instância"
},
"app.install.phase.preparing_java": {
"message": "Preparando Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Baixando Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extraindo Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Obtendo Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparando Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validando Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Lendo manifestação de pacote"
},
"app.install.phase.resolving_loader": {
"message": "Resolvendo loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Resolvendo Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Resolvendo conteúdo"
},
"app.install.phase.rolling_back": {
"message": "Indo de volta"
},
"app.install.phase.running_loader_processors": {
"message": "Executando processadores de loader"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos os dados para sua instância serão excluídos permanentemente, incluindo seus mundos, configs e todo o conteúdo instalado."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instância"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Instalando {current, number}/{total, number} projeto(s)..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalizando atualização..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Resolvendo versões..."
},
"app.instance.mods.content-type-project": {
"message": "projeto"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Excluir mundo"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Excluir mundo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Este mundo será permanentemente excluído desta instância. Esta ação não pode ser desfeita."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Excluindo {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modificado"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Nenhum servidor ou mundo adicionado"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Remover servidor"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Remover servidor"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "O servidor será removido da lista de servidores e da lista de servidores no jogo. Você pode re-adicioná-lo depois se souber o endereço."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Removendo {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar {count} mundos..."
},
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "Copiar endereço"
},
"instance.worlds.create_shortcut": {
"message": "Criar atalho"
},
"instance.worlds.dont_show_on_home": {
"message": "Não exibir na página inicial"
},
+6 -120
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Скрыть другие активные сборки"
},
"app.action-bar.install.dismiss": {
"message": "Закрыть"
},
"app.action-bar.install.failed": {
"message": "Установка не удалась"
},
"app.action-bar.install.failed-app-closed": {
"message": "Установка не удалась из-за закрытия приложения."
},
"app.action-bar.install.failed-network": {
"message": "Установка не удалась из-за сетевой ошибки."
},
"app.action-bar.install.failed-unknown": {
"message": "Установка не удалась из-за неизвестной ошибки."
},
"app.action-bar.install.open-instance": {
"message": "Открыть сборку"
},
"app.action-bar.install.retry": {
"message": "Повторить"
},
"app.action-bar.install.unknown-instance": {
"message": "Неизвестная сборка"
},
"app.action-bar.installs": {
"message": "Установки"
},
"app.action-bar.make-primary-instance": {
"message": "Сделать основной сборкой"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Время игры"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Автоматически пропускает подтверждения с низким уровнем риска, такие как установка дубликатов сборок, обычное удаление контента, массовые обновления, отключение сборок и запросы на восстановление. Опасные предупреждения по-прежнему будут отображаться."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Пропустить несущественные предупреждения"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Показывать кнопку скрытия боковой панели."
},
@@ -203,69 +170,24 @@
"app.export-modal.header": {
"message": "Экспорт сборки"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Добавить «{file}» в экспорт?"
},
"app.export-modal.modpack-name-label": {
"message": "Название сборки"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Название сборки"
},
"app.export-modal.select-files-label": {
"message": "Выберите файлы для экспорта"
},
"app.export-modal.version-number-label": {
"message": "Номер версии"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Скачивание контента"
},
"app.install.phase.downloading_minecraft": {
"message": "Скачивание Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Скачивание файла сборки"
},
"app.install.phase.extracting_overrides": {
"message": "Извлечение переопределений"
},
"app.install.phase.finalizing": {
"message": "Завершение"
},
"app.install.phase.preparing_instance": {
"message": "Подготовка сборки"
},
"app.install.phase.preparing_java": {
"message": "Подготовка Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Скачивание Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Распаковка Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Получение Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Подготовка Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Проверка Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Чтение манифеста сборки"
},
"app.install.phase.resolving_minecraft": {
"message": "Загрузка версий"
},
"app.install.phase.resolving_pack": {
"message": "Загрузка контента"
},
"app.install.phase.rolling_back": {
"message": "Откат"
},
"app.install.phase.running_loader_processors": {
"message": "Запуск процессоров загрузчика"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Все данные сборки будут удалены навсегда, в том числе миры, настройки и весь установленный контент."
},
@@ -290,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Сборка"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Скачивание {current, number}/{total, number} проектов..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Завершение обновления..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Загрузка версий..."
},
"app.instance.mods.content-type-project": {
"message": "проект"
},
@@ -320,18 +233,6 @@
"app.instance.worlds.browse-servers": {
"message": "Найти серверы"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Удалить"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Удаление мира"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Этот мир будет удалён навсегда из этой сборки. Отменить это действие нельзя."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Удаление {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Модовые"
},
@@ -350,18 +251,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Нет серверов и миров"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Удалить"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Удаление сервера"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Этот сервер будет удалён из списка серверов, в том числе и в игре. Его можно вернуть, зная адрес."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Удаление {name}"
},
"app.modal.install-to-play.content-required": {
"message": "Требуется дополнительный контент"
},
@@ -941,9 +830,6 @@
"instance.worlds.copy_address": {
"message": "Копировать адрес"
},
"instance.worlds.create_shortcut": {
"message": "Создать ярлык"
},
"instance.worlds.dont_show_on_home": {
"message": "Не показывать на главной"
},
+1 -10
View File
@@ -1,10 +1,4 @@
{
"app.action-bar.install.failed": {
"message": "Neuspešna instalacija"
},
"app.action-bar.install.failed-app-closed": {
"message": "Neuspešna instalacija zbog zatvaranja aplikacije."
},
"app.auth-servers.unreachable.body": {
"message": "Minecraft serveri za autentifikaciju su možda trenutno nedostupni. Molimo vas da proverite vašu internet vezu i pokušajte ponovo kasnije."
},
@@ -162,14 +156,11 @@
"message": "Minecraft svet"
},
"instance.edit-world.title": {
"message": "Promeni svet"
"message": "Izmeni svet"
},
"instance.server-modal.placeholder-name": {
"message": "Minecraft Server"
},
"instance.settings.tabs.general": {
"message": "Generalno"
},
"instance.settings.tabs.general.deleting.button": {
"message": "Brisanje..."
},
+7 -133
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Göm fler aktiva instanser"
},
"app.action-bar.install.dismiss": {
"message": "Avvisa"
},
"app.action-bar.install.failed": {
"message": "Installationen misslyckades"
},
"app.action-bar.install.failed-app-closed": {
"message": "Installationen misslyckades för att appen stängdes."
},
"app.action-bar.install.failed-network": {
"message": "Installationen misslyckades av ett nätverksfel."
},
"app.action-bar.install.failed-unknown": {
"message": "Installationen misslyckades med ett okänt fel."
},
"app.action-bar.install.open-instance": {
"message": "Öppna instans"
},
"app.action-bar.install.retry": {
"message": "Försök igen"
},
"app.action-bar.install.unknown-instance": {
"message": "Okänd instans"
},
"app.action-bar.installs": {
"message": "Installationer"
},
"app.action-bar.make-primary-instance": {
"message": "Gör till primärinstans"
},
@@ -51,7 +24,7 @@
"message": "Primärinstans"
},
"app.action-bar.reload-to-update": {
"message": "Uppdatera (ladda om)"
"message": "Ladda om för att uppdatera"
},
"app.action-bar.show-more-running-instances": {
"message": "Visa fler aktiva instanser"
@@ -101,15 +74,9 @@
"app.appearance-settings.hide-nametag.title": {
"message": "Dölj namnskylt"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "Inkluderar de senaste världarna i \"Hoppa tillbaka i världar\" på hemsidan."
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "Hoppa tillbaka i världar"
},
"app.appearance-settings.minimize-launcher.description": {
"message": "Minimerar launchern när Minecraft startas."
},
"app.appearance-settings.minimize-launcher.title": {
"message": "Minimera programstartare"
},
@@ -128,12 +95,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Visa speltid"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Hoppar över bekräftelser med låg risk, såsom installation av dubbla modpaket, radering av vanligt innehåll, massuppdateringar, avlänkning av modpaket och reparationsmeddelanden. Varningar om farliga händelser visas fortfarande."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Hoppa över icke nödvändiga varningar"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Låter dig växla sidofältet."
},
@@ -185,9 +146,6 @@
"app.browse.project-type.modpacks": {
"message": "Modpaket"
},
"app.browse.server-instance-content-warning": {
"message": "Att lägga till innehåll kan bryta kompatibiliteten när du ansluter dig till servern. Allt tillagt innehåll kommer att förloras när du uppdaterar serverinstansen."
},
"app.browse.server.installing": {
"message": "Installerar"
},
@@ -203,66 +161,24 @@
"app.export-modal.header": {
"message": "Exportera modpaket"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Inkludera \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpaketets namn"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpaketets namn"
},
"app.export-modal.select-files-label": {
"message": "Konfigurera vilka filer som inkluderas i denna export"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Laddar ner innehåll"
},
"app.install.phase.downloading_minecraft": {
"message": "Laddar ner Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Laddar ner pack fil"
},
"app.install.phase.extracting_overrides": {
"message": "Extraherar överskrivningar"
},
"app.install.phase.finalizing": {
"message": "Avslutar"
},
"app.install.phase.preparing_instance": {
"message": "Förbereder instans"
},
"app.install.phase.preparing_java": {
"message": "Förbereder Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Laddar ner Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extraherar Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Hämtar Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Förbereder Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Bekräftar Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Läser pack information"
},
"app.install.phase.resolving_loader": {
"message": "Laddar ner loadern"
},
"app.install.phase.resolving_minecraft": {
"message": "Laddar ner Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Laddar ner innehåll"
},
"app.instance.confirm-delete.admonition-body": {
"message": "All data från din instans kommer permanent raderas, däribland dina världar, konfigurationer samt allt installerat innehåll."
},
@@ -287,15 +203,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instans"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Laddar ner {current, number}/{total, number} projekt..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Slutför uppdatering..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Laddar versioner..."
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
@@ -320,15 +227,6 @@
"app.instance.worlds.browse-servers": {
"message": "Utforska servrar"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Ta bort värld"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Ta bort värld"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Raderar {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Moddad"
},
@@ -347,15 +245,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Inga servrar eller världar tillagda"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Ta bort server"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Ta bort server"
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Raderar {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Sök {count} världar..."
},
@@ -398,9 +287,6 @@
"app.project.install-button.already-installed": {
"message": "Detta projekt är redan installerat"
},
"app.project.install-context.back-to-browse": {
"message": "Tillbaka till upptäck"
},
"app.project.install-context.install-content-to-instance": {
"message": "Installera innehåll till instans"
},
@@ -515,12 +401,6 @@
"app.skins.rate-limit.title": {
"message": "Sakta ner!"
},
"app.skins.reorder-error.text": {
"message": "Ditt utseende kunde inte sparas."
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
@@ -545,9 +425,6 @@
"app.skins.section.saved-skins": {
"message": "Sparade utseenden"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "The Copper Age"
},
@@ -938,9 +815,6 @@
"instance.worlds.copy_address": {
"message": "Kopiera adress"
},
"instance.worlds.create_shortcut": {
"message": "Skapa genväg"
},
"instance.worlds.dont_show_on_home": {
"message": "Visa inte på Startsidan"
},
@@ -170,12 +170,18 @@
"app.export-modal.header": {
"message": "ส่งออกแพ็กม็อด"
},
"app.export-modal.include-file-accessibility-label": {
"message": "รวมถึง \"{file}\" ใช่หรือไม่"
},
"app.export-modal.modpack-name-label": {
"message": "ชื่อแพ็กม็อด"
},
"app.export-modal.modpack-name-placeholder": {
"message": "ชื่อแพ็กม็อด"
},
"app.export-modal.select-files-label": {
"message": "กำหนดไฟล์ที่ต้องการจะส่งออก"
},
"app.export-modal.version-number-label": {
"message": "หมายเลขเวอร์ชัน"
},
+6 -123
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "Diğer çalışan kurulumları gizle"
},
"app.action-bar.install.dismiss": {
"message": "Reddet"
},
"app.action-bar.install.failed": {
"message": "Yükleme yapılamadı"
},
"app.action-bar.install.failed-app-closed": {
"message": "Yükleme uygulama kapandığından yapılamadı."
},
"app.action-bar.install.failed-network": {
"message": "Yükleme ağ hatasından dolayı yapılamadı."
},
"app.action-bar.install.failed-unknown": {
"message": "Yükleme bilinmeyen bir hatadan yapılamadı."
},
"app.action-bar.install.open-instance": {
"message": "Kurulumu aç"
},
"app.action-bar.install.retry": {
"message": "Yeniden Dene"
},
"app.action-bar.install.unknown-instance": {
"message": "Bilinmeyen kurulum"
},
"app.action-bar.installs": {
"message": "Yüklenenler"
},
"app.action-bar.make-primary-instance": {
"message": "Birincil kurulum yap"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "Oynama Süresini Göster"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Mükerrer mod paketi kurulumları, normal içerik silme, toplu güncellemeler, mod paketi bağlantısını kesme ve onarım uyarıları gibi düşük riskli onayları otomatik olarak atlar. Tehlikeli uyarılar gösterilmeye devam edecektir."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Gerekli olmayan uyarıları atla"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Kenar çubuğunu ayarlama özelliğini aktifleştirir."
},
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "Mod paketini dışa aktar"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\" dahil mi?"
},
"app.export-modal.modpack-name-label": {
"message": "Mod Paketi Adı"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Mod paketi adı"
},
"app.export-modal.select-files-label": {
"message": "Bu dışa aktarmaya hangi dosyaların dahil edileceğini yapılandırma"
},
"app.export-modal.version-number-label": {
"message": "Sürüm numarası"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "İçerik indiriliyor"
},
"app.install.phase.downloading_minecraft": {
"message": "Minecraft yükleniyor"
},
"app.install.phase.downloading_pack_file": {
"message": "Paket dosyaları yükleniyor"
},
"app.install.phase.extracting_overrides": {
"message": "Geçersiz kılmalar ayıklanıyor"
},
"app.install.phase.finalizing": {
"message": "Tamamlanıyor"
},
"app.install.phase.preparing_instance": {
"message": "Kurulum hazırlanıyor"
},
"app.install.phase.preparing_java": {
"message": "Java Hazırlanıyor"
},
"app.install.phase.preparing_java.downloading": {
"message": "Java {version} İndiriliyor"
},
"app.install.phase.preparing_java.extracting": {
"message": "Java {version} ayıklanıyor"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Java {version} getiriliyor"
},
"app.install.phase.preparing_java.resolving": {
"message": "Java {version} Hazırlanıyor"
},
"app.install.phase.preparing_java.validating": {
"message": "Java {version} doğrulanıyor"
},
"app.install.phase.reading_pack_manifest": {
"message": "Paket manifesti okunuyor"
},
"app.install.phase.resolving_loader": {
"message": "Yükleyici çözümleniyor"
},
"app.install.phase.resolving_minecraft": {
"message": "Minecraft çözümleniyor"
},
"app.install.phase.resolving_pack": {
"message": "İçerik çözümleniyor"
},
"app.install.phase.rolling_back": {
"message": "Değişiklikler geri alınıyor"
},
"app.install.phase.running_loader_processors": {
"message": "Yükleyici işlemcileri çalıştırılıyor"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Dünyalarınız, yapılandırmalarınız ve yüklü tüm içerikler ve tüm veriler kalıcı olarak silinecek."
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "Kurulum"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} proje indiriliyor..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Güncelleme tamamlanıyor..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Sürümler çözümleniyor..."
},
"app.instance.mods.content-type-project": {
"message": "proje"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "Sunucuları Keşfet"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Dünya Sil"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Dünyayı sil"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Bu dünya, bu profilden kalıcı olarak silinecek. Bu işlem geri alınamaz."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "{name} Siliniyor"
},
"app.instance.worlds.filter-modded": {
"message": "Modlu"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Eklenmiş sunucu veya dünya yok"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Sunucuyu kaldır"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Sunucuyu kaldır"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Bu sunucu, sunucu listenizden ve oyun içi sunucu listesinden kaldırılacaktır. Adresini biliyorsanız daha sonra tekrar ekleyebilirsiniz."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "{name} kaldırılıyor"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count} dünya ara..."
},
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "Adresi kopyala"
},
"instance.worlds.create_shortcut": {
"message": "Kısayol yap"
},
"instance.worlds.dont_show_on_home": {
"message": "Ana sayfada gösterme"
},
@@ -170,12 +170,18 @@
"app.export-modal.header": {
"message": "Експортувати збірку"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Уключити «{file}»?"
},
"app.export-modal.modpack-name-label": {
"message": "Назва збірки"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Назва збірки"
},
"app.export-modal.select-files-label": {
"message": "Змініть файли, які додані до експорту"
},
"app.export-modal.version-number-label": {
"message": "Номер версії"
},
@@ -170,12 +170,18 @@
"app.export-modal.header": {
"message": "Xuất modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Bao gồm \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Tên modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Tên modpack"
},
"app.export-modal.select-files-label": {
"message": "Cấu hình các tệp nào được bao gồm trong quá trình xuất phiên bản này"
},
"app.export-modal.version-number-label": {
"message": "Phiên bản"
},
+15 -132
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "隐藏更多运行中的实例"
},
"app.action-bar.install.dismiss": {
"message": "忽略"
},
"app.action-bar.install.failed": {
"message": "安装失败"
},
"app.action-bar.install.failed-app-closed": {
"message": "由于应用关闭,安装失败。"
},
"app.action-bar.install.failed-network": {
"message": "由于网络错误,安装失败。"
},
"app.action-bar.install.failed-unknown": {
"message": "由于未知错误,安装失败。"
},
"app.action-bar.install.open-instance": {
"message": "打开实例"
},
"app.action-bar.install.retry": {
"message": "重试"
},
"app.action-bar.install.unknown-instance": {
"message": "未知的实例"
},
"app.action-bar.installs": {
"message": "安装"
},
"app.action-bar.make-primary-instance": {
"message": "设为主实例"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "显示总游玩时间"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "自动跳过低风险确认,包括重复安装整合包、常规内容删除、批量更新、解除整合包关联及修复提示。危险警告仍会显示。"
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "跳过非必要的警告"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "启用切换侧边栏的功能。"
},
@@ -141,7 +108,7 @@
"message": "切换侧边栏"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "如果你尝试安装一个未托管在 Modrinth 上的 Modrinth 整合包文件(.mrpack),我们会确保你在安装之前了解相关风险。"
"message": "如果你尝试安装一个未托管在 Modrinth 上的 Modrinth 包文件(.mrpack),我们会确保你在安装之前了解相关风险。"
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "安装未知整合包前提醒我"
@@ -153,7 +120,7 @@
"message": "无法连接到身份验证服务器"
},
"app.browse.add-servers-to-instance": {
"message": "添加服务器到实例"
"message": "正在添加服务器到实例"
},
"app.browse.add-to-an-instance": {
"message": "添加到实例"
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "导出整合包"
},
"app.export-modal.include-file-accessibility-label": {
"message": "包含 \"{file}\""
},
"app.export-modal.modpack-name-label": {
"message": "整合包名称"
},
"app.export-modal.modpack-name-placeholder": {
"message": "整合包名称"
},
"app.export-modal.select-files-label": {
"message": "配置此导出中包含哪些文件"
},
"app.export-modal.version-number-label": {
"message": "版本号"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "正在下载内容"
},
"app.install.phase.downloading_minecraft": {
"message": "正在下载 Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "正在下载包文件"
},
"app.install.phase.extracting_overrides": {
"message": "提取覆盖项"
},
"app.install.phase.finalizing": {
"message": "完成"
},
"app.install.phase.preparing_instance": {
"message": "准备实例"
},
"app.install.phase.preparing_java": {
"message": "正在准备 Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "正在下载 Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "正在提取 Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "正在获取 Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "正在准备 Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "正在校验 Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "正在读取整合包清单"
},
"app.install.phase.resolving_loader": {
"message": "正在解析加载器"
},
"app.install.phase.resolving_minecraft": {
"message": "正在解析 Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "正在解析内容"
},
"app.install.phase.rolling_back": {
"message": "回滚"
},
"app.install.phase.running_loader_processors": {
"message": "正在运行加载处理器"
},
"app.instance.confirm-delete.admonition-body": {
"message": "你实例的所有数据将被永久删除,包括你的世界、配置和所有已安装的内容。"
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "实例"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "正在下载 {current, number}/{total, number}个 项目……"
},
"app.instance.mods.bulk-update.finishing": {
"message": "正在结束更新……"
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "正在解析版本……"
},
"app.instance.mods.content-type-project": {
"message": "项目"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "浏览服务器"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "删除世界"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "删除世界"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "这个世界将会被永久删除,此操作无法撤销。"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "正在删除 {name}"
},
"app.instance.worlds.filter-modded": {
"message": "模组适配"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "未添加服务器或世界"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "删除服务器"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "删除服务器"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "该服务器将会被移出你的服务器列表,也包括游戏内的服务器列表。之后如果你记得地址,仍可将其添加回来。"
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "正在删除 {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜索 {count} 个世界……"
},
@@ -765,7 +651,7 @@
"message": "将从设备中永久删除该实例,包括所有存档、配置文件及已安装内容。此操作不可撤销,请谨慎操作。"
},
"instance.settings.tabs.general.deleting.button": {
"message": "删除中……"
"message": "删除中..."
},
"instance.settings.tabs.general.duplicate-button": {
"message": "复制"
@@ -846,7 +732,7 @@
"message": "在游戏关闭后运行。"
},
"instance.settings.tabs.hooks.post-exit.enter": {
"message": "输入退出后运行的命令…"
"message": "输入退出后运行的命令…"
},
"instance.settings.tabs.hooks.pre-launch": {
"message": "启动前"
@@ -855,7 +741,7 @@
"message": "在实例启动前运行。"
},
"instance.settings.tabs.hooks.pre-launch.enter": {
"message": "输入启动前命令……"
"message": "输入启动前命令..."
},
"instance.settings.tabs.hooks.title": {
"message": "游戏启动钩子"
@@ -867,7 +753,7 @@
"message": "用于启动 Minecraft 的包装器命令。"
},
"instance.settings.tabs.hooks.wrapper.enter": {
"message": "输入封装命令……"
"message": "输入封装命令..."
},
"instance.settings.tabs.installation": {
"message": "安装"
@@ -933,7 +819,7 @@
"message": "游戏启动时的窗口高度。"
},
"instance.settings.tabs.window.height.enter": {
"message": "输入高度…"
"message": "输入高度…"
},
"instance.settings.tabs.window.width": {
"message": "宽度"
@@ -942,7 +828,7 @@
"message": "游戏启动时的窗口宽度。"
},
"instance.settings.tabs.window.width.enter": {
"message": "输入宽度…"
"message": "输入宽度…"
},
"instance.worlds.a_minecraft_server": {
"message": "Minecraft 服务器"
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "复制地址"
},
"instance.worlds.create_shortcut": {
"message": "创建快照"
},
"instance.worlds.dont_show_on_home": {
"message": "在主界面隐藏"
},
@@ -1044,7 +927,7 @@
"message": "仍然安装"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "恶意软件常常通过整合包文件在 Discord 等平台上分享而传播。"
"message": "恶意软件常常通过模组包文件在 Discord 等平台上分享而传播。"
},
"unknown-pack-warning-modal.warning.body": {
"message": "我们在 Modrinth 上找不到此文件。强烈建议你仅从可信来源安装文件。"
+7 -124
View File
@@ -11,33 +11,6 @@
"app.action-bar.hide-more-running-instances": {
"message": "隱藏更多執行中的實例"
},
"app.action-bar.install.dismiss": {
"message": "忽略"
},
"app.action-bar.install.failed": {
"message": "無法安裝"
},
"app.action-bar.install.failed-app-closed": {
"message": "由於應用程式關閉,無法安裝。"
},
"app.action-bar.install.failed-network": {
"message": "由於網路錯誤,無法安裝。"
},
"app.action-bar.install.failed-unknown": {
"message": "由於未知錯誤,無法安裝。"
},
"app.action-bar.install.open-instance": {
"message": "開啟實例"
},
"app.action-bar.install.retry": {
"message": "重試"
},
"app.action-bar.install.unknown-instance": {
"message": "未知的實例"
},
"app.action-bar.installs": {
"message": "安裝"
},
"app.action-bar.make-primary-instance": {
"message": "設為主要實例"
},
@@ -128,12 +101,6 @@
"app.appearance-settings.show-play-time.title": {
"message": "顯示遊玩時數"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "自動跳過低風險操作的確認提示,例如重複安裝模組包、常規內容刪除、批次更新、取消連結模組包以及修復提示。高風險警告仍會顯示。"
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "跳過非必要的警告"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "啟用切換側邊欄的功能。"
},
@@ -195,7 +162,7 @@
"message": "沒有符合 {compatibilityLabel} 的可用版本。請選擇一個版本強制安裝。不會自動安裝相依模組。"
},
"app.export-modal.description-placeholder": {
"message": "輸入模組包描述……"
"message": "輸入模組包描述..."
},
"app.export-modal.export-button": {
"message": "匯出"
@@ -203,72 +170,24 @@
"app.export-modal.header": {
"message": "匯出模組包"
},
"app.export-modal.include-file-accessibility-label": {
"message": "要包含「{file}」嗎?"
},
"app.export-modal.modpack-name-label": {
"message": "模組包名稱"
},
"app.export-modal.modpack-name-placeholder": {
"message": "模組包名稱"
},
"app.export-modal.select-files-label": {
"message": "設定要包含在此匯出檔案中的檔案"
},
"app.export-modal.version-number-label": {
"message": "版本號碼"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "正在下載內容"
},
"app.install.phase.downloading_minecraft": {
"message": "正在下載 Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "正在下載模組包檔案"
},
"app.install.phase.extracting_overrides": {
"message": "正在解壓縮覆寫檔案"
},
"app.install.phase.finalizing": {
"message": "即將完成"
},
"app.install.phase.preparing_instance": {
"message": "正在準備實例"
},
"app.install.phase.preparing_java": {
"message": "正在準備 Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "正在下載 Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "正在解壓縮 Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "正在擷取 Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "正在準備 Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "正在驗證 Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "正在讀取模組包清單"
},
"app.install.phase.resolving_loader": {
"message": "正在解析載入器"
},
"app.install.phase.resolving_minecraft": {
"message": "正在解析 Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "正在解析內容"
},
"app.install.phase.rolling_back": {
"message": "正在復原"
},
"app.install.phase.running_loader_processors": {
"message": "正在執行載入器處理程式"
},
"app.instance.confirm-delete.admonition-body": {
"message": "你實例中的所有資料將被永久刪除,包含你的世界、設定檔以及所有已安裝的內容。"
},
@@ -293,15 +212,6 @@
"app.instance.modpack-already-installed.instance": {
"message": "實例"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "正在下載專案 {current, number}/{total, number}..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "正在結束更新..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "正在解析版本..."
},
"app.instance.mods.content-type-project": {
"message": "專案"
},
@@ -326,18 +236,6 @@
"app.instance.worlds.browse-servers": {
"message": "瀏覽伺服器"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "刪除世界"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "刪除世界"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "此世界將會被永久刪除,此操作無法復原。"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "正在刪除 {name}"
},
"app.instance.worlds.filter-modded": {
"message": "模組"
},
@@ -356,18 +254,6 @@
"app.instance.worlds.no-worlds-heading": {
"message": "未新增任何伺服器或世界"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "移除伺服器"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "移除伺服器"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "這個伺服器將從你的伺服器清單中移除。如果你記得伺服器位址,稍後可以將其重新新增回來。"
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "正在移除 {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜尋 {count} 個世界..."
},
@@ -953,9 +839,6 @@
"instance.worlds.copy_address": {
"message": "複製位址"
},
"instance.worlds.create_shortcut": {
"message": "建立捷徑"
},
"instance.worlds.dont_show_on_home": {
"message": "不要在首頁顯示"
},
+4 -9
View File
@@ -22,8 +22,6 @@ import {
preferencesDiffer,
provideBrowseManager,
requestInstall,
stripServerRuntimeInstallFilters,
stripServerRuntimeInstallOverrides,
useBrowseSearch,
useDebugLogger,
useVIntl,
@@ -405,7 +403,7 @@ const messages = defineMessages({
},
hideAddedServers: {
id: 'app.browse.hide-added-servers',
defaultMessage: 'Hide servers already added',
defaultMessage: 'Hide already added servers',
},
installingToServer: {
id: 'app.browse.server.installing',
@@ -682,6 +680,7 @@ async function chooseInstanceInstallVersion(
const selectedVersion = getLatestMatchingInstallVersion(
await getInstallProjectVersions(project.project_id),
selectedPreferences,
projectTypeValue,
)
if (!selectedVersion) {
@@ -764,15 +763,11 @@ function getCardActions(
project: projectResult,
contentType,
mode: isModpack ? 'immediate' : 'queue',
selectedFilters: isModpack
? []
: stripServerRuntimeInstallFilters(searchState.currentFilters.value),
selectedFilters: isModpack ? [] : searchState.currentFilters.value,
providedFilters: isModpack ? [] : combinedProvidedFilters.value,
overriddenProvidedFilterTypes: isModpack
? []
: stripServerRuntimeInstallOverrides(
searchState.overriddenProvidedFilterTypes.value,
),
: searchState.overriddenProvidedFilterTypes.value,
targetPreferences: getServerInstallTargetPreferences(contentType),
getProjectVersions: getInstallProjectVersions,
queue: serverInstallQueue,
-6
View File
@@ -114,10 +114,6 @@ const messages = defineMessages({
id: 'app.skins.section.tiny-takeover',
defaultMessage: 'Tiny Takeover',
},
chaosCubedSection: {
id: 'app.skins.section.chaos-cubed',
defaultMessage: 'Chaos Cubed',
},
rateLimitTitle: {
id: 'app.skins.rate-limit.title',
defaultMessage: 'Slow down!',
@@ -451,8 +447,6 @@ function getDefaultSkinSectionTitle(section?: string) {
return formatMessage(messages.mountsOfMayhemSection)
case 'Tiny Takeover':
return formatMessage(messages.tinyTakeoverSection)
case 'Chaos Cubed':
return formatMessage(messages.chaosCubedSection)
case 'Default skins':
return formatMessage(messages.defaultSkinsSection)
default:
@@ -1,11 +1,7 @@
<template>
<div
class="h-full w-full pt-6"
:class="isContainedServerRoute ? 'box-border min-h-0 overflow-hidden' : ''"
>
<div class="h-full w-full pt-6">
<ServersManageRootLayout
:server-id="serverId"
:layout-mode="isContainedServerRoute ? 'contained' : 'page'"
:reload-page="() => router.go(0)"
:resolve-viewer="resolveViewer"
:show-copy-id-action="themeStore.devMode"
@@ -67,8 +63,6 @@ const queryClient = useQueryClient()
const themeStore = useTheming()
const breadcrumbs = useBreadcrumbs()
const isContainedServerRoute = computed(() => route.name === 'ServerManageOverview')
const serverId = computed(() => {
const rawId = route.params.id
return Array.isArray(rawId) ? rawId[0] : (rawId ?? '')
@@ -3,5 +3,5 @@ import { ServersManageOverviewPage } from '@modrinth/ui'
</script>
<template>
<ServersManageOverviewPage contained-console />
<ServersManageOverviewPage />
</template>
+11 -44
View File
@@ -317,10 +317,6 @@ import ContextMenu from '@/components/ui/ContextMenu.vue'
import ExportModal from '@/components/ui/ExportModal.vue'
import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.vue'
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
import {
fetchCachedServerStatus,
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import { useInstanceConsole } from '@/composables/useInstanceConsole'
import { trackEvent } from '@/helpers/analytics'
import { get_project_v3 } from '@/helpers/cache.js'
@@ -331,7 +327,7 @@ import { type InstanceContentData, loadInstanceContentData } from '@/helpers/ins
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { createInstanceShortcut, showInstanceInFolder } from '@/helpers/utils.js'
import { refreshWorlds, type ServerStatus } from '@/helpers/worlds'
import { get_server_status, refreshWorlds } from '@/helpers/worlds'
import { injectServerInstall } from '@/providers/server-install'
import { handleSevereError } from '@/store/error.js'
import { useBreadcrumbs, useTheming } from '@/store/state'
@@ -376,15 +372,13 @@ const selected = ref<unknown[]>([])
const minecraftServer = computed(() => linkedProjectV3.value?.minecraft_server)
const javaServerPingData = computed(() => linkedProjectV3.value?.minecraft_java_server?.ping?.data)
const liveServerStatusOnline = ref(false)
const statusOnline = computed(() => liveServerStatusOnline.value || !!javaServerPingData.value)
const statusOnline = computed(() => !!javaServerPingData.value)
const recentPlays = computed(
() => linkedProjectV3.value?.minecraft_java_server?.verified_plays_2w ?? undefined,
)
const playersOnline = ref<number | undefined>(undefined)
const ping = ref<number | undefined>(undefined)
const loadingServerPing = ref(false)
const activeInstanceId = ref<string>()
watch(
() => router.currentRoute.value,
@@ -396,20 +390,6 @@ watch(
{ immediate: true },
)
function applyServerStatus(status: ServerStatus) {
playersOnline.value = status.players?.online
ping.value = status.ping
liveServerStatusOnline.value = true
loadingServerPing.value = true
}
function resetServerStatus() {
ping.value = undefined
playersOnline.value = undefined
liveServerStatusOnline.value = false
loadingServerPing.value = false
}
function isContentSubpageRoute(routeName = displayedInstanceRoute.value.name) {
return typeof routeName === 'string' && contentSubpageRouteNames.has(routeName)
}
@@ -418,7 +398,9 @@ async function fetchInstance() {
isServerInstance.value = false
linkedProjectV3.value = undefined
preloadedContent.value = null
resetServerStatus()
ping.value = undefined
playersOnline.value = undefined
loadingServerPing.value = false
const nextInstance = await get(route.params.id as string).catch(handleError)
let nextLinkedProjectV3: Labrinth.Projects.v3.Project | undefined
@@ -447,9 +429,8 @@ async function fetchInstance() {
linkedProjectV3.value = nextLinkedProjectV3
isServerInstance.value = nextIsServerInstance
preloadedContent.value = nextPreloadedContent
activeInstanceId.value = nextInstance?.id
fetchDeferredData(nextInstance?.id)
fetchDeferredData()
if (nextInstance) {
queryClient.prefetchQuery({
@@ -460,32 +441,18 @@ async function fetchInstance() {
}
}
function fetchDeferredData(instanceId?: string) {
function fetchDeferredData() {
const serverAddress = linkedProjectV3.value?.minecraft_java_server?.address
if (isServerInstance.value && serverAddress) {
const cachedStatus = getFreshCachedServerStatus(queryClient, serverAddress)
if (cachedStatus) {
applyServerStatus(cachedStatus)
} else {
playersOnline.value = undefined
ping.value = undefined
loadingServerPing.value = false
}
fetchCachedServerStatus(queryClient, serverAddress)
get_server_status(serverAddress)
.then((status) => {
if (
activeInstanceId.value !== instanceId ||
linkedProjectV3.value?.minecraft_java_server?.address !== serverAddress
)
return
applyServerStatus(status)
playersOnline.value = status.players?.online
ping.value = status.ping
})
.catch((error) => {
console.error(`Failed to fetch server status for ${serverAddress}:`, error)
})
.finally(() => {
if (activeInstanceId.value !== instanceId) return
loadingServerPing.value = true
})
} else {
@@ -709,7 +676,7 @@ const handleOptionsClick = async (args: { option: string; item: unknown }) => {
break
case 'copy_path': {
if (instance.value) {
const fullPath = await get_full_path(instance.value.id)
const fullPath = await get_full_path(instance.value?.path)
await navigator.clipboard.writeText(fullPath)
}
break
@@ -1351,9 +1351,7 @@ provideContentManager({
title: item.file_name.replace('.disabled', ''),
icon_url: null,
},
projectLink: item.project?.id
? { path: `/project/${item.project.id}`, query: { i: props.instance.id } }
: undefined,
projectLink: item.project?.id ? { path: `/project/${item.project.id}` } : undefined,
version: item.version ?? {
id: item.file_name,
version_number: formatMessage(commonMessages.unknownLabel),
@@ -1363,7 +1361,6 @@ provideContentManager({
item.project?.id && item.version?.id
? {
path: `/project/${item.project.id}/version/${item.version.id}`,
query: { i: props.instance.id },
}
: undefined,
owner: item.owner
@@ -1446,7 +1443,7 @@ onMounted(() => {
props.instance &&
event.instance_id === props.instance.id &&
event.event === 'synced' &&
props.instance.install_stage === 'installed' &&
props.instance.install_stage !== 'pack_installing' &&
!isBulkOperating.value
) {
await initProjects()
@@ -618,7 +618,10 @@ const filterOptions = computed(() => {
const hasSingleplayer = dedupedWorlds.value.some((x) => x.type === 'singleplayer')
const hasServer = dedupedWorlds.value.some((x) => x.type === 'server')
if (hasSingleplayer && hasServer) {
const hasStatusFilter =
selectedFilters.value.includes('online') || selectedFilters.value.includes('offline')
if (hasSingleplayer && hasServer && !hasStatusFilter) {
options.push({ id: 'singleplayer', label: formatMessage(commonMessages.singleplayerLabel) })
}
@@ -630,11 +633,13 @@ const filterOptions = computed(() => {
options.push({ id: 'vanilla', label: formatMessage(messages.vanillaFilter) })
options.push({ id: 'modded', label: formatMessage(messages.moddedFilter) })
}
const hasOnline = servers.some((x) => !!serverData.value[x.address]?.status)
const hasOffline = servers.some((x) => !serverData.value[x.address]?.status)
if (hasOnline && hasOffline) {
options.push({ id: 'online', label: formatMessage(messages.onlineFilter) })
options.push({ id: 'offline', label: formatMessage(messages.offlineFilter) })
if (!selectedFilters.value.includes('singleplayer')) {
const hasOnline = servers.some((x) => !!serverData.value[x.address]?.status)
const hasOffline = servers.some((x) => !serverData.value[x.address]?.status)
if (hasOnline && hasOffline) {
options.push({ id: 'online', label: formatMessage(messages.onlineFilter) })
options.push({ id: 'offline', label: formatMessage(messages.offlineFilter) })
}
}
}
+8 -51
View File
@@ -116,27 +116,13 @@
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #open-in-browser> <ExternalIcon /> Open in browser </template>
<template #report> <ReportIcon /> Report </template>
</OverflowMenu>
</ButtonStyled>
</template>
<template v-else #actions>
<ButtonStyled v-if="showSwitchVersion && onVersionsPage" size="large">
<button v-tooltip="installButtonTooltip" disabled>
<CheckIcon />
{{ formatMessage(commonMessages.installedLabel) }}
</button>
</ButtonStyled>
<ButtonStyled v-else-if="showSwitchVersion" size="large">
<button @click="goToVersions">
<SwapIcon />
{{ formatMessage(messages.switchVersion) }}
</button>
</ButtonStyled>
<ButtonStyled v-else size="large" color="brand">
<ButtonStyled size="large" color="brand">
<button
v-tooltip="installButtonTooltip"
:disabled="installButtonDisabled"
@@ -185,9 +171,7 @@
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #open-in-browser> <ExternalIcon /> Open in browser </template>
<template #follow> <HeartIcon /> Follow </template>
<template #save> <BookmarkIcon /> Save </template>
<template #report> <ReportIcon /> Report </template>
@@ -302,7 +286,6 @@ import {
SelectedProjectsFloatingBar,
useVIntl,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { convertFileSrc } from '@tauri-apps/api/core'
import { openUrl } from '@tauri-apps/plugin-opener'
import dayjs from 'dayjs'
@@ -310,13 +293,8 @@ import relativeTime from 'dayjs/plugin/relativeTime'
import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { SwapIcon } from '@/assets/icons/index.js'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import InstanceIndicator from '@/components/ui/InstanceIndicator.vue'
import {
fetchCachedServerStatus,
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import {
get_organization,
get_project,
@@ -335,7 +313,7 @@ import {
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
import { get_by_instance_id } from '@/helpers/process'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { getServerAddress } from '@/helpers/worlds'
import { getServerAddress, getServerLatency } from '@/helpers/worlds'
import { injectContentInstall } from '@/providers/content-install'
import { injectServerInstall } from '@/providers/server-install'
import { createServerInstallContent } from '@/providers/setup/server-install-content'
@@ -348,7 +326,6 @@ const { handleError } = injectNotificationManager()
const { install: installVersion } = injectContentInstall()
const route = useRoute()
const router = useRouter()
const queryClient = useQueryClient()
const breadcrumbs = useBreadcrumbs()
const themeStore = useTheming()
const { formatMessage } = useVIntl()
@@ -366,10 +343,6 @@ const messages = defineMessages({
id: 'app.project.install-button.already-installed',
defaultMessage: 'This project is already installed',
},
switchVersion: {
id: 'app.project.install-button.switch-version',
defaultMessage: 'Switch version',
},
})
const { installingServerProjects, playServerProject, showAddServerToInstanceModal } =
@@ -541,13 +514,6 @@ const installButtonTooltip = computed(() => {
return null
})
const showSwitchVersion = computed(() => !!instance.value && installed.value)
const onVersionsPage = computed(() => route.name === 'Versions')
function goToVersions() {
router.push(versionsHref.value)
}
const [allLoaders, allGameVersions] = await Promise.all([
get_loaders().catch(handleError).then(ref),
get_game_versions().catch(handleError).then(ref),
@@ -634,19 +600,10 @@ async function fetchProjectData() {
function fetchDeferredServerData(project) {
const serverAddress = projectV3.value?.minecraft_java_server?.address
if (serverAddress) {
const cachedStatus = getFreshCachedServerStatus(queryClient, serverAddress)
if (cachedStatus) {
serverPing.value = cachedStatus.ping
serverStatusOnline.value = true
} else {
serverPing.value = undefined
}
fetchCachedServerStatus(queryClient, serverAddress)
.then((status) => {
if (projectV3.value?.minecraft_java_server?.address !== serverAddress) return
serverPing.value = status.ping
serverStatusOnline.value = true
serverPing.value = undefined
getServerLatency(serverAddress)
.then((latency) => {
serverPing.value = latency
})
.catch((error) => {
console.error(`Failed to ping server ${serverAddress}:`, error)
+426 -162
View File
@@ -1,152 +1,259 @@
<template>
<div class="flex flex-col">
<router-link
class="mb-4 flex w-fit items-center gap-2 rounded-lg px-2 py-0.5 pl-0 text-link"
:to="buildProjectHref(`/project/${route.params.id}/versions`)"
>
<ChevronLeftIcon class="shrink-0" /> {{ formatMessage(messages.allVersions) }}
</router-link>
<VersionPage
v-if="version"
:version="version"
:enrichment="enrichment"
:enrichment-loading="enrichmentLoading"
:members="members"
:dependency-link-creator="createDependencyLink"
>
<template #headerActions>
<div>
<Card>
<Breadcrumbs
:current-title="version.name"
:link-stack="[
{
href: buildProjectHref(`/project/${route.params.id}/versions`),
label: 'Versions',
},
]"
/>
<div class="version-title">
<h2>{{ version.name }}</h2>
</div>
<div class="button-group">
<ButtonStyled color="brand">
<button
:disabled="installing || (installed && installedVersion === version.id)"
@click="() => version && install(version.id)"
@click="() => install(version.id)"
>
<DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installedVersion !== version.id" />
<CheckIcon v-else />
{{
installing
? formatMessage(messages.installing)
? 'Installing...'
: installed && installedVersion === version.id
? formatMessage(commonMessages.installedLabel)
: installed
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(commonMessages.installButton)
? 'Installed'
: 'Install'
}}
</button>
</ButtonStyled>
<ButtonStyled type="outlined" circular>
<OverflowMenu
v-tooltip="formatMessage(commonMessages.moreOptionsButton)"
:options="[
{
id: 'open-in-browser',
link: `https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`,
external: true,
},
{
id: 'report',
color: 'red',
hoverFilled: true,
link: `https://modrinth.com/report?item=version&itemID=${version.id}`,
external: true,
},
]"
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #report>
<ReportIcon aria-hidden="true" /> {{ formatMessage(commonMessages.reportButton) }}
</template>
</OverflowMenu>
</ButtonStyled>
</template>
<template #supplementaryResourceActions="{ file }">
<ButtonStyled>
<a :href="file.url" :download="file.filename" target="_blank">
<DownloadIcon aria-hidden="true" />
{{ formatMessage(messages.downloadInBrowser) }}
<button>
<ReportIcon />
Report
</button>
</ButtonStyled>
<ButtonStyled>
<a
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
rel="external"
>
Modrinth website
<ExternalIcon />
</a>
</ButtonStyled>
</template>
</VersionPage>
</div>
</Card>
<div class="version-container">
<div class="description-cards">
<Card>
<h3 class="card-title">Changelog</h3>
<div class="markdown-body" v-html="renderString(version.changelog ?? '')" />
</Card>
<Card>
<h3 class="card-title">Files</h3>
<Card
v-for="file in version.files"
:key="file.id"
:class="{ primary: file.primary }"
class="file"
>
<span class="label">
<FileIcon />
<span>
<span class="title">
{{ file.filename }}
</span>
({{ formatBytes(file.size) }})
<span v-if="file.primary" class="primary-label"> Primary </span>
</span>
</span>
<ButtonStyled v-if="project.project_type !== 'modpack' || file.primary" color="brand">
<button class="download" :disabled="installed" @click="() => install(version.id)">
<DownloadIcon v-if="!installed" />
<CheckIcon v-else />
{{ installed ? 'Installed' : 'Install' }}
</button>
</ButtonStyled>
</Card>
</Card>
<Card v-if="displayDependencies.length > 0">
<h2>Dependencies</h2>
<div v-for="dependency in displayDependencies" :key="dependency.title">
<router-link v-if="dependency.link" class="btn dependency" :to="dependency.link">
<Avatar size="sm" :src="dependency.icon" />
<div>
<span class="title"> {{ dependency.title }} </span> <br />
<span> {{ dependency.subtitle }} </span>
</div>
</router-link>
<div v-else class="dependency disabled" disabled="">
<Avatar size="sm" :src="dependency.icon" />
<div class="text">
<div class="title">{{ dependency.title }}</div>
<div>{{ dependency.subtitle }}</div>
</div>
</div>
</div>
</Card>
</div>
<Card class="metadata-card">
<h3 class="card-title">Metadata</h3>
<div class="metadata">
<div class="metadata-item">
<span class="metadata-label">Release Channel</span>
<span class="metadata-value"
><Badge
:color="releaseColor(version.version_type)"
:type="
version.version_type.charAt(0).toUpperCase() + version.version_type.slice(1)
"
/></span>
</div>
<div class="metadata-item">
<span class="metadata-label">Version Number</span>
<span class="metadata-value">{{ version.version_number }}</span>
</div>
<div class="metadata-item">
<span class="metadata-label">Loaders</span>
<span class="metadata-value">{{
version.loaders
.map((loader) => loader.charAt(0).toUpperCase() + loader.slice(1))
.join(', ')
}}</span>
</div>
<div class="metadata-item">
<span class="metadata-label">Game Versions</span>
<span class="metadata-value"> {{ version.game_versions.join(', ') }} </span>
</div>
<div class="metadata-item">
<span class="metadata-label">Downloads</span>
<span class="metadata-value">{{ version.downloads }}</span>
</div>
<div class="metadata-item">
<span class="metadata-label">Publication Date</span>
<span class="metadata-value">
{{ formatDateTime(version.date_published) }}
</span>
</div>
<div v-if="author" class="metadata-item">
<span class="metadata-label">Author</span>
<a
:href="`https://modrinth.com/user/${author.user.username}`"
rel="external"
class="metadata-value btn author"
>
<Avatar size="sm" :src="author.user.avatar_url" circle />
<span>
<strong>
{{ author.user.username }}
</strong>
<br />
{{ author.role }}
</span>
</a>
</div>
<div class="metadata-item">
<span class="metadata-label">Version ID</span>
<span class="metadata-value"><CopyCode class="copycode" :text="version.id" /></span>
</div>
</div>
</Card>
</div>
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
CheckIcon,
ChevronLeftIcon,
DownloadIcon,
ExternalIcon,
MoreVerticalIcon,
ReportIcon,
} from '@modrinth/assets'
<script setup>
import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets'
import {
Avatar,
Badge,
Breadcrumbs,
ButtonStyled,
commonMessages,
defineMessages,
type DependencyContext,
OverflowMenu,
useVIntl,
VersionPage,
Card,
CopyCode,
useFormatBytes,
useFormatDateTime,
} from '@modrinth/ui'
import { ref, watch } from 'vue'
import { renderString } from '@modrinth/utils'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { SwapIcon } from '@/assets/icons'
import { get_project_many, get_version_many } from '@/helpers/cache.js'
import { releaseColor } from '@/helpers/utils'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const { formatMessage } = useVIntl()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
})
const formatBytes = useFormatBytes()
const messages = defineMessages({
allVersions: {
id: 'app.project.version.all-versions',
defaultMessage: 'All versions',
const breadcrumbs = useBreadcrumbs()
const route = useRoute()
const props = defineProps({
project: {
type: Object,
required: true,
},
versions: {
type: Array,
required: true,
},
members: {
type: Array,
required: true,
},
install: {
type: Function,
required: true,
},
installed: {
type: Boolean,
required: true,
},
installing: {
id: 'app.project.version.installing',
defaultMessage: 'Installing',
type: Boolean,
required: true,
},
downloadInBrowser: {
id: 'app.project.version.download-in-browser',
defaultMessage: 'Download in browser',
installedVersion: {
type: String,
required: true,
},
})
const breadcrumbs = useBreadcrumbs()
const route = useRoute()
const props = defineProps<{
project: Labrinth.Projects.v2.Project
versions: Labrinth.Versions.v3.Version[]
members: Labrinth.Projects.v3.TeamMember[]
install: (version: string | null) => void
installed: boolean
installing: boolean
installedVersion: string
}>()
const version = ref(props.versions.find((version) => version.id === route.params.version))
if (version.value) {
breadcrumbs.setName('Version', version.value.name)
}
breadcrumbs.setName('Version', version.value.name)
const enrichment = ref<Labrinth.Projects.v2.DependencyInfo | undefined>(undefined)
const enrichmentLoading = ref(false)
watch(
() => props.versions,
async () => {
if (route.params.version) {
version.value = props.versions.find((version) => version.id === route.params.version)
await refreshDisplayDependencies()
breadcrumbs.setName('Version', version.value.name)
}
},
)
function buildProjectHref(path: string): string {
const author = computed(() =>
props.members ? props.members.find((member) => member.user.id === version.value.author_id) : null,
)
const displayDependencies = ref({})
function buildProjectHref(path) {
const params = new URLSearchParams()
for (const [key, val] of Object.entries(route.query)) {
if (Array.isArray(val)) {
for (const v of val) {
if (v != null) params.append(key, v)
}
for (const v of val) params.append(key, v)
} else if (val) {
params.append(key, String(val))
}
@@ -155,65 +262,222 @@ function buildProjectHref(path: string): string {
return qs ? `${path}?${qs}` : path
}
function createDependencyLink(context: DependencyContext): string | undefined {
if (context.version) {
return buildProjectHref(`/project/${context.version.project_id}/version/${context.version.id}`)
}
if (context.project) {
return buildProjectHref(`/project/${context.project.id}`)
}
return undefined
}
async function refreshEnrichment() {
if (!version.value) return
const projectIds = new Set<string>()
const versionIds = new Set<string>()
for (const dependency of version.value.dependencies ?? []) {
if (dependency.project_id) {
projectIds.add(dependency.project_id)
}
if (dependency.version_id) {
versionIds.add(dependency.version_id)
}
}
if (projectIds.size === 0 && versionIds.size === 0) {
enrichment.value = { projects: [], versions: [] }
return
}
enrichmentLoading.value = true
try {
const versionResults = versionIds.size > 0 ? await get_version_many([...versionIds]) : []
for (const dependencyVersion of versionResults ?? []) {
if (dependencyVersion.project_id) {
projectIds.add(dependencyVersion.project_id)
async function refreshDisplayDependencies() {
const projectIds = new Set()
const versionIds = new Set()
if (version.value.dependencies) {
for (const dependency of version.value.dependencies) {
if (dependency.project_id) {
projectIds.add(dependency.project_id)
}
if (dependency.version_id) {
versionIds.add(dependency.version_id)
}
}
const projectResults = projectIds.size > 0 ? await get_project_many([...projectIds]) : []
enrichment.value = {
projects: projectResults ?? [],
versions: versionResults ?? [],
}
} finally {
enrichmentLoading.value = false
}
}
const [projectDeps, versionDeps] = await Promise.all([
get_project_many([...projectIds]),
get_version_many([...versionIds]),
])
watch(
() => props.versions,
async () => {
if (route.params.version) {
version.value = props.versions.find((v) => v.id === route.params.version)
if (version.value) {
breadcrumbs.setName('Version', version.value.name)
const dependencies = {
projects: projectDeps,
versions: versionDeps,
}
displayDependencies.value = version.value.dependencies.map((dependency) => {
const version = dependencies.versions.find((obj) => obj.id === dependency.version_id)
if (version) {
const project = dependencies.projects.find(
(obj) => obj.id === version.project_id || obj.id === dependency.project_id,
)
return {
icon: project?.icon_url,
title: project?.title || project?.name,
subtitle: `Version ${version.version_number} is ${dependency.dependency_type}`,
link: buildProjectHref(`/project/${project.slug}/version/${version.id}`),
}
await refreshEnrichment()
}
},
)
} else {
const project = dependencies.projects.find((obj) => obj.id === dependency.project_id)
await refreshEnrichment()
if (project) {
return {
icon: project?.icon_url,
title: project?.title || project?.name,
subtitle: `${dependency.dependency_type}`,
link: buildProjectHref(`/project/${project.slug}`),
}
} else {
return {
icon: null,
title: dependency.file_name,
subtitle: `Added via overrides`,
link: null,
}
}
}
})
}
await refreshDisplayDependencies()
</script>
<style scoped lang="scss">
.version-container {
display: flex;
flex-direction: row;
gap: 1rem;
}
.version-title {
margin-bottom: 1rem;
h2 {
font-size: var(--font-size-2xl);
font-weight: 700;
color: var(--color-contrast);
margin: 0;
}
}
.dependency {
display: flex;
padding: 0.5rem 1rem 0.5rem 0.5rem;
gap: 0.5rem;
background: var(--color-raised-bg);
color: var(--color-base);
width: 100%;
.title {
font-weight: bolder;
}
:deep(svg) {
margin-right: 0 !important;
}
}
.file {
display: flex;
flex-direction: row;
gap: 0.5rem;
background: var(--color-button-bg);
color: var(--color-base);
padding: 0.5rem 1rem;
.download {
margin-left: auto;
background-color: var(--color-raised-bg);
}
.label {
display: flex;
margin: auto 0 auto;
gap: 0.5rem;
.title {
font-weight: bolder;
word-break: break-all;
}
svg {
min-width: 1.1rem;
min-height: 1.1rem;
width: 1.1rem;
height: 1.1rem;
margin: auto 0;
}
.primary-label {
font-style: italic;
}
}
}
.primary {
background: var(--color-brand-highlight);
color: var(--color-contrast);
}
.button-group {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 0.5rem;
}
.card-title {
font-size: var(--font-size-lg);
color: var(--color-contrast);
margin: 0 0 0.5rem;
}
.description-cards {
width: 100%;
}
.metadata-card {
width: 20rem;
height: min-content;
}
.metadata {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 1rem;
.metadata-item {
display: flex;
flex-direction: column;
gap: 0.5rem;
.metadata-label {
font-weight: bold;
}
}
}
.author {
display: flex;
flex-direction: row;
gap: 0.5rem;
align-items: center;
text-decoration: none;
color: var(--color-base);
background: var(--color-raised-bg);
padding: 0.5rem;
width: 100%;
box-shadow: none;
}
.markdown-body {
:deep(hr),
:deep(h1),
:deep(h2),
img {
max-width: max(60rem, 90%) !important;
}
:deep(ul),
:deep(ol) {
margin-left: 2rem;
}
}
.copycode {
border: 0;
color: var(--color-contrast);
}
.disabled {
display: flex;
flex-direction: row;
vertical-align: center;
align-items: center;
cursor: not-allowed;
border-radius: var(--radius-lg);
.text {
filter: brightness(0.5);
}
}
</style>
@@ -5,23 +5,16 @@
:game-versions="gameVersions"
:versions="versions"
:project="project"
:show-environment-column="themeStore.featureFlags.show_version_environment_column"
:version-link="(version) => buildProjectHref(`/project/${project.id}/version/${version.id}`)"
>
<template #actions="{ version }">
<ButtonStyled
circular
type="transparent"
:color="installed && version.id === installedVersion ? 'standard' : 'green'"
>
<ButtonStyled circular type="transparent">
<button
v-tooltip="
!installed
? formatMessage(commonMessages.installButton)
: version.id !== installedVersion
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(messages.alreadyInstalled)
"
v-tooltip="`Install`"
:class="{
'group-hover:!bg-brand group-hover:[&>svg]:!text-brand-inverted':
!installed || version.id !== installedVersion,
}"
:disabled="installing || (installed && version.id === installedVersion)"
@click.stop="() => install(version.id)"
>
@@ -33,6 +26,7 @@
<ButtonStyled circular type="transparent">
<OverflowMenu
v-if="false"
class="group-hover:!bg-button-bg"
:options="[
{
id: 'install-elsewhere',
@@ -53,13 +47,12 @@
<DownloadIcon aria-hidden="true" />
Add to another instance
</template>
<template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #open-in-browser> <ExternalIcon /> Open in browser </template>
</OverflowMenu>
<a
v-else
v-tooltip="formatMessage(commonMessages.openInBrowserButton)"
v-tooltip="`Open in browser`"
class="group-hover:!bg-button-bg"
:href="`https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`"
target="_blank"
>
@@ -75,29 +68,15 @@
import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets'
import {
ButtonStyled,
commonMessages,
defineMessages,
injectNotificationManager,
OverflowMenu,
ProjectPageVersions,
useVIntl,
} from '@modrinth/ui'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { SwapIcon } from '@/assets/icons/index.js'
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
import { useTheming } from '@/store/theme.ts'
const { formatMessage } = useVIntl()
const themeStore = useTheming()
const messages = defineMessages({
alreadyInstalled: {
id: 'app.project.versions.already-installed',
defaultMessage: 'Already installed',
},
})
defineProps({
project: {
-1
View File
@@ -247,7 +247,6 @@ export default new createRouter({
name: 'Logs',
component: Instance.Logs,
meta: {
renderMode: 'fixed',
useRootContext: true,
breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Logs' }],
},
-1
View File
@@ -8,7 +8,6 @@ export const DEFAULT_FEATURE_FLAGS = {
worlds_tab: false,
worlds_in_home: true,
server_project_qa: false,
show_version_environment_column: false,
server_ram_as_bytes_always_on: false,
always_show_app_controls: false,
skip_non_essential_warnings: false,
-2
View File
@@ -27,8 +27,6 @@ pub enum ErrorKind {
inner: Box<s3::error::S3Error>,
file: String,
},
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Error acquiring semaphore: {0}")]
Acquire(#[from] tokio::sync::AcquireError),
#[error("Tracing error: {0}")]
+93 -300
View File
@@ -1,19 +1,3 @@
//! Fetches Fabric-compatible loader metadata.
//!
//! Fabric and Quilt both expose loader profiles for a concrete Minecraft
//! version, but Daedalus publishes templated profiles using
//! `${modrinth.gameVersion}`. A group is a set of Minecraft versions whose
//! upstream loader profiles have the same structure after the concrete
//! Minecraft version is replaced with `${modrinth.gameVersion}`. Fabric uses
//! one universal group, so its public profile paths stay as
//! `versions/{loader}.json`. Quilt has more than one group: versions before
//! 26.x include hashed/intermediary libraries, while 26.x versions do not. For
//! Quilt, Daedalus writes one templated profile per group at
//! `version-group/{group}/loader-version/{loader}`.
use crate::metadata_groups::{
UNIVERSAL_METADATA_GROUP, metadata_group_for_game_version, metadata_groups,
};
use crate::util::{download_file, fetch_json, format_url};
use crate::{
Error, FetchResult, MirrorArtifact, UploadFile, insert_mirrored_artifact,
@@ -80,112 +64,7 @@ async fn fetch(
&semaphore,
)
.await?;
let all_loader_versions = fabric_manifest.loader.clone();
let all_game_versions = fabric_manifest.game.clone();
let metadata_groups = metadata_groups(
mod_loader,
all_game_versions.iter().map(|x| x.version.as_str()),
);
if metadata_groups
.iter()
.any(|group| group.id != UNIVERSAL_METADATA_GROUP)
{
let loaders = all_loader_versions
.iter()
.filter(|x| !skip_versions.contains(&&*x.version))
.collect::<Vec<_>>();
let profile_requests = metadata_groups
.iter()
.flat_map(|group| {
loaders.iter().map(move |loader| ProfileRequest {
group: group.id.to_string(),
loader_profile_template_game_version: group
.loader_profile_template_game_version
.clone(),
game_versions: group.game_versions.clone(),
loader_version: loader.version.clone(),
url: format!(
"{}/versions/loader/{}/{}/profile/json",
meta_url,
group.loader_profile_template_game_version,
loader.version
),
})
})
.collect::<Vec<_>>();
fetch_metadata_profiles(
mod_loader,
format_version,
maven_url,
profile_requests,
&upload_files,
&mirror_artifacts,
&semaphore,
)
.await?;
let version_groups = metadata_groups
.iter()
.map(|group| daedalus::modded::VersionGroup {
id: group.id.to_string(),
loaders: loaders
.iter()
.map(|loader| {
let version_path = metadata_version_path(
mod_loader,
format_version,
&loader.version,
group.id,
);
daedalus::modded::LoaderVersion {
id: loader.version.clone(),
url: format_url(&version_path),
stable: loader.stable,
}
})
.collect(),
})
.collect();
let manifest = daedalus::modded::Manifest {
game_versions: all_game_versions
.into_iter()
.map(|game_version| {
let group = metadata_group_for_game_version(
&metadata_groups,
mod_loader,
&game_version.version,
)
.expect("game version should have a metadata group");
daedalus::modded::Version {
id: game_version.version.clone(),
stable: game_version.stable,
version_group: Some(group.id.to_string()),
loaders: Vec::new(),
}
})
.collect(),
version_groups,
};
upload_files.insert(
format!("{mod_loader}/v{format_version}/manifest.json"),
UploadFile {
file: bytes::Bytes::from(serde_json::to_vec(&manifest)?),
content_type: Some("application/json".to_string()),
},
);
return Ok(FetchResult {
upload_files,
mirror_artifacts,
});
}
// We check Modrinth's manifest to find newly added loader versions,
// intermediary/mapping artifacts, and game versions.
let (
@@ -246,6 +125,8 @@ async fn fetch(
)
};
const DUMMY_GAME_VERSION: &str = "1.21";
if !fetch_intermediary_versions.is_empty() {
for x in &fetch_intermediary_versions {
insert_mirrored_artifact(
@@ -259,38 +140,94 @@ async fn fetch(
}
if !fetch_fabric_versions.is_empty() {
let universal_group = metadata_groups
let fabric_version_manifest_urls = fetch_fabric_versions
.iter()
.find(|group| group.id == UNIVERSAL_METADATA_GROUP)
.expect("fabric metadata should have a universal group");
let profile_requests = fetch_fabric_versions
.iter()
.map(|loader| ProfileRequest {
group: universal_group.id.to_string(),
loader_profile_template_game_version: universal_group
.loader_profile_template_game_version
.clone(),
game_versions: universal_group.game_versions.clone(),
loader_version: loader.version.clone(),
url: format!(
.map(|x| {
format!(
"{}/versions/loader/{}/{}/profile/json",
meta_url,
universal_group.loader_profile_template_game_version,
loader.version
),
meta_url, DUMMY_GAME_VERSION, x.version
)
})
.collect::<Vec<_>>();
fetch_metadata_profiles(
mod_loader,
format_version,
maven_url,
profile_requests,
&upload_files,
&mirror_artifacts,
&semaphore,
let fabric_version_manifests = futures::future::try_join_all(
fabric_version_manifest_urls
.iter()
.map(|x| download_file(x, None, &semaphore)),
)
.await?;
.await?
.into_iter()
.map(|x| serde_json::from_slice(&x))
.collect::<Result<Vec<PartialVersionInfo>, serde_json::Error>>()?;
let patched_version_manifests = fabric_version_manifests
.into_iter()
.map(|mut version_info| {
for lib in &mut version_info.libraries {
let new_name = lib
.name
.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
// Hard-code: This library is not present on fabric's maven, so we fetch it from MC libraries
if &*lib.name == "net.minecraft:launchwrapper:1.12" {
lib.url = Some(
"https://libraries.minecraft.net/".to_string(),
);
}
// If a library is not intermediary, we add it to mirror artifacts to be mirrored
if lib.name == new_name {
insert_mirrored_artifact(
&new_name,
None,
vec![
lib.url
.clone()
.unwrap_or_else(|| maven_url.to_string()),
],
false,
&mirror_artifacts,
)?;
} else {
lib.name = new_name;
}
lib.url = Some(format_url("maven/"));
}
version_info.id = version_info
.id
.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
version_info.inherits_from = version_info
.inherits_from
.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
Ok(version_info)
})
.collect::<Result<Vec<_>, Error>>()?;
let serialized_version_manifests = patched_version_manifests
.iter()
.map(|x| serde_json::to_vec(x).map(bytes::Bytes::from))
.collect::<Result<Vec<_>, serde_json::Error>>()?;
serialized_version_manifests
.into_iter()
.enumerate()
.for_each(|(index, bytes)| {
let loader = fetch_fabric_versions[index];
let version_path = format!(
"{mod_loader}/v{format_version}/versions/{}.json",
loader.version
);
upload_files.insert(
version_path,
UploadFile {
file: bytes,
content_type: Some("application/json".to_string()),
},
);
});
}
if !fetch_fabric_versions.is_empty()
@@ -303,20 +240,17 @@ async fn fetch(
let loader_versions = daedalus::modded::Version {
id: DUMMY_REPLACE_STRING.to_string(),
stable: true,
version_group: None,
loaders: all_loader_versions
.iter()
.filter(|x| !skip_versions.contains(&&*x.version))
loaders: fabric_manifest
.loader
.into_iter()
.map(|x| {
let version_path = metadata_version_path(
mod_loader,
format_version,
&x.version,
UNIVERSAL_METADATA_GROUP,
let version_path = format!(
"{mod_loader}/v{format_version}/versions/{}.json",
x.version,
);
daedalus::modded::LoaderVersion {
id: x.version.clone(),
id: x.version,
url: format_url(&version_path),
stable: x.stable,
}
@@ -326,16 +260,14 @@ async fn fetch(
let manifest = daedalus::modded::Manifest {
game_versions: std::iter::once(loader_versions)
.chain(all_game_versions.into_iter().map(|x| {
.chain(fabric_manifest.game.into_iter().map(|x| {
daedalus::modded::Version {
id: x.version,
stable: x.stable,
version_group: None,
loaders: vec![],
}
}))
.collect(),
version_groups: Vec::new(),
};
upload_files.insert(
@@ -353,145 +285,6 @@ async fn fetch(
})
}
struct ProfileRequest {
group: String,
loader_profile_template_game_version: String,
game_versions: Vec<String>,
loader_version: String,
url: String,
}
fn metadata_version_path(
mod_loader: &str,
format_version: usize,
loader_version: &str,
group: &str,
) -> String {
if group == UNIVERSAL_METADATA_GROUP {
format!("{mod_loader}/v{format_version}/versions/{loader_version}.json")
} else {
format!(
"{mod_loader}/v{format_version}/version-group/{group}/loader-version/{loader_version}"
)
}
}
async fn fetch_metadata_profiles(
mod_loader: &str,
format_version: usize,
maven_url: &str,
profile_requests: Vec<ProfileRequest>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
semaphore: &Arc<Semaphore>,
) -> Result<(), Error> {
let version_manifests = futures::future::try_join_all(
profile_requests
.iter()
.map(|x| download_file(&x.url, None, semaphore)),
)
.await?
.into_iter()
.map(|x| serde_json::from_slice(&x))
.collect::<Result<Vec<PartialVersionInfo>, serde_json::Error>>()?;
let patched_version_manifests = version_manifests
.into_iter()
.zip(profile_requests.iter())
.map(|(mut version_info, request)| {
patch_version_info(
&mut version_info,
&request.loader_profile_template_game_version,
&request.game_versions,
maven_url,
mirror_artifacts,
)?;
Ok(version_info)
})
.collect::<Result<Vec<_>, Error>>()?;
let serialized_version_manifests = patched_version_manifests
.iter()
.map(|x| serde_json::to_vec(x).map(bytes::Bytes::from))
.collect::<Result<Vec<_>, serde_json::Error>>()?;
serialized_version_manifests
.into_iter()
.zip(profile_requests)
.for_each(|(bytes, request)| {
let version_path = metadata_version_path(
mod_loader,
format_version,
&request.loader_version,
&request.group,
);
upload_files.insert(
version_path,
UploadFile {
file: bytes,
content_type: Some("application/json".to_string()),
},
);
});
Ok(())
}
fn patch_version_info(
version_info: &mut PartialVersionInfo,
game_version: &str,
game_versions: &[String],
maven_url: &str,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
for lib in &mut version_info.libraries {
let new_name = lib.name.replace(game_version, DUMMY_REPLACE_STRING);
// Hard-code: This library is not present on fabric's maven, so we fetch it from MC libraries
if &*lib.name == "net.minecraft:launchwrapper:1.12" {
lib.url = Some("https://libraries.minecraft.net/".to_string());
}
let source_url =
lib.url.clone().unwrap_or_else(|| maven_url.to_string());
if lib.name == new_name {
insert_mirrored_artifact(
&new_name,
None,
vec![source_url],
false,
mirror_artifacts,
)?;
} else {
for concrete_game_version in game_versions {
let concrete_name =
lib.name.replace(game_version, concrete_game_version);
insert_mirrored_artifact(
&concrete_name,
None,
vec![source_url.clone()],
false,
mirror_artifacts,
)?;
}
lib.name = new_name;
}
lib.url = Some(format_url("maven/"));
}
version_info.id =
version_info.id.replace(game_version, DUMMY_REPLACE_STRING);
version_info.inherits_from = version_info
.inherits_from
.replace(game_version, DUMMY_REPLACE_STRING);
Ok(())
}
#[derive(Deserialize, Debug, Clone)]
struct FabricVersions {
pub loader: Vec<FabricLoaderVersion>,
+20 -58
View File
@@ -12,10 +12,7 @@ use itertools::Itertools;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::sync::Arc;
use tokio::sync::Semaphore;
#[tracing::instrument(skip(semaphore))]
@@ -246,45 +243,12 @@ async fn fetch(
};
if !fetch_versions.is_empty() {
let total_installers = fetch_versions.len();
let downloaded_installers = Arc::new(AtomicUsize::new(0));
tracing::info!(
mod_loader,
total_files = total_installers,
"Downloading loader installers"
);
let forge_installers =
futures::future::try_join_all(fetch_versions.iter().map(|x| {
let downloaded_installers = downloaded_installers.clone();
let semaphore = semaphore.clone();
async move {
let installer =
download_file(&x.installer_url, None, &semaphore)
.await?;
let downloaded = downloaded_installers
.fetch_add(1, Ordering::Relaxed)
+ 1;
if downloaded.is_multiple_of(100)
|| downloaded == total_installers
{
tracing::info!(
mod_loader,
downloaded_files = downloaded,
remaining_files =
total_installers.saturating_sub(downloaded),
total_files = total_installers,
"Downloaded loader installers"
);
}
Ok::<_, Error>(installer)
}
}))
.await?;
let forge_installers = futures::future::try_join_all(
fetch_versions
.iter()
.map(|x| download_file(&x.installer_url, None, &semaphore)),
)
.await?;
#[tracing::instrument(skip(raw, upload_files, mirror_artifacts))]
async fn read_forge_installer(
@@ -797,23 +761,21 @@ async fn fetch(
.into_iter()
.map(|(game_version, loaders)| {
daedalus::modded::Version {
id: game_version,
stable: true,
version_group: None,
loaders: loaders
.map(|x| daedalus::modded::LoaderVersion {
url: format_url(&format!(
"{mod_loader}/v{format_version}/versions/{}.json",
x.loader_version
)),
id: x.loader_version,
stable: false,
})
.collect(),
}
id: game_version,
stable: true,
loaders: loaders
.map(|x| daedalus::modded::LoaderVersion {
url: format_url(&format!(
"{mod_loader}/v{format_version}/versions/{}.json",
x.loader_version
)),
id: x.loader_version,
stable: false,
})
.collect(),
}
})
.collect(),
version_groups: Vec::new(),
};
upload_files.insert(
+41 -208
View File
@@ -1,14 +1,10 @@
use crate::util::{
REQWEST_CLIENT, format_url, upload_file_to_bucket,
upload_url_to_bucket_mirrors, write_file_to_local_output,
write_url_to_local_output_mirrors,
upload_url_to_bucket_mirrors,
};
use daedalus::get_path_from_artifact;
use dashmap::{DashMap, DashSet};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::sync::Arc;
use tokio::sync::Semaphore;
use tracing_error::ErrorLayer;
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
@@ -16,7 +12,6 @@ use tracing_subscriber::{EnvFilter, fmt, prelude::*};
mod error;
mod fabric;
mod forge;
mod metadata_groups;
mod minecraft;
pub mod util;
@@ -49,92 +44,51 @@ async fn main() -> Result<()> {
));
let mut fetch_result = FetchResult::default();
let only_loader = dotenvy::var("DAEDALUS_ONLY").ok();
if should_fetch(only_loader.as_deref(), "minecraft") {
match minecraft::fetch(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Minecraft fetch failed"),
}
match minecraft::fetch(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Minecraft fetch failed"),
}
if should_fetch(only_loader.as_deref(), "fabric") {
match fabric::fetch_fabric(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Fabric fetch failed"),
}
match fabric::fetch_fabric(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Fabric fetch failed"),
}
if should_fetch(only_loader.as_deref(), "quilt") {
match fabric::fetch_quilt(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Quilt fetch failed"),
}
match fabric::fetch_quilt(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Quilt fetch failed"),
}
if should_fetch(only_loader.as_deref(), "neo") {
match forge::fetch_neo(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "NeoForge fetch failed"),
}
match forge::fetch_neo(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "NeoForge fetch failed"),
}
if should_fetch(only_loader.as_deref(), "forge") {
match forge::fetch_forge(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Forge fetch failed"),
}
match forge::fetch_forge(semaphore.clone()).await {
Ok(fetched) => merge_fetch_result(&mut fetch_result, fetched),
Err(err) => tracing::warn!(error = %err, "Forge fetch failed"),
}
let FetchResult {
upload_files,
mirror_artifacts,
} = fetch_result;
let upload_file_total = upload_files.len();
let mirror_file_total = mirror_artifacts.len();
if dotenvy::var("LOCAL_OUTPUT_DIR").is_ok() {
let written_files = Arc::new(AtomicUsize::new(0));
futures::future::try_join_all(upload_files.iter().map(|entry| {
upload_file_to_bucket(
entry.key().clone(),
entry.value().file.clone(),
entry.value().content_type.clone(),
&semaphore,
)
}))
.await?;
tracing::info!(
total_files = upload_file_total,
"Writing local metadata files"
);
futures::future::try_join_all(upload_files.iter().map(|entry| {
let path = entry.key().clone();
let file = entry.value().file.clone();
let written_files = written_files.clone();
async move {
write_file_to_local_output(&path, file).await?;
let written = written_files.fetch_add(1, Ordering::Relaxed) + 1;
if written.is_multiple_of(100) || written == upload_file_total {
tracing::info!(
written_files = written,
remaining_files =
upload_file_total.saturating_sub(written),
total_files = upload_file_total,
"Wrote local metadata files"
);
}
Ok::<_, Error>(())
}
}))
.await?;
let written_mirror_files = Arc::new(AtomicUsize::new(0));
tracing::info!(
total_files = mirror_file_total,
"Writing local mirror files"
);
futures::future::try_join_all(mirror_artifacts.iter().map(|entry| {
let path = format!("maven/{}", entry.key());
let mirrors = entry
futures::future::try_join_all(mirror_artifacts.iter().map(|entry| {
upload_url_to_bucket_mirrors(
format!("maven/{}", entry.key()),
entry
.value()
.mirrors
.iter()
@@ -145,117 +99,12 @@ async fn main() -> Result<()> {
format!("{}{}", mirror.path, entry.key())
}
})
.collect();
let sha1 = entry.value().sha1.clone();
let written_mirror_files = written_mirror_files.clone();
let semaphore = semaphore.clone();
async move {
write_url_to_local_output_mirrors(
path, mirrors, sha1, &semaphore,
)
.await?;
let written =
written_mirror_files.fetch_add(1, Ordering::Relaxed) + 1;
if written.is_multiple_of(100) || written == mirror_file_total {
tracing::info!(
written_files = written,
remaining_files =
mirror_file_total.saturating_sub(written),
total_files = mirror_file_total,
"Wrote local mirror files"
);
}
Ok::<_, Error>(())
}
}))
.await?;
} else {
let uploaded_files = Arc::new(AtomicUsize::new(0));
tracing::info!(
total_files = upload_file_total,
"Uploading metadata files"
);
futures::future::try_join_all(upload_files.iter().map(|entry| {
let path = entry.key().clone();
let file = entry.value().file.clone();
let content_type = entry.value().content_type.clone();
let uploaded_files = uploaded_files.clone();
let semaphore = semaphore.clone();
async move {
upload_file_to_bucket(path, file, content_type, &semaphore)
.await?;
let uploaded =
uploaded_files.fetch_add(1, Ordering::Relaxed) + 1;
if uploaded.is_multiple_of(100) || uploaded == upload_file_total
{
tracing::info!(
uploaded_files = uploaded,
remaining_files =
upload_file_total.saturating_sub(uploaded),
total_files = upload_file_total,
"Uploaded metadata files"
);
}
Ok::<_, Error>(())
}
}))
.await?;
let uploaded_mirror_files = Arc::new(AtomicUsize::new(0));
tracing::info!(
total_files = mirror_file_total,
"Uploading mirror files"
);
futures::future::try_join_all(mirror_artifacts.iter().map(|entry| {
let path = format!("maven/{}", entry.key());
let mirrors = entry
.value()
.mirrors
.iter()
.map(|mirror| {
if mirror.entire_url {
mirror.path.clone()
} else {
format!("{}{}", mirror.path, entry.key())
}
})
.collect();
let sha1 = entry.value().sha1.clone();
let uploaded_mirror_files = uploaded_mirror_files.clone();
let semaphore = semaphore.clone();
async move {
upload_url_to_bucket_mirrors(path, mirrors, sha1, &semaphore)
.await?;
let uploaded =
uploaded_mirror_files.fetch_add(1, Ordering::Relaxed) + 1;
if uploaded.is_multiple_of(100) || uploaded == mirror_file_total
{
tracing::info!(
uploaded_files = uploaded,
remaining_files =
mirror_file_total.saturating_sub(uploaded),
total_files = mirror_file_total,
"Uploaded mirror files"
);
}
Ok::<_, Error>(())
}
}))
.await?;
}
.collect(),
entry.value().sha1.clone(),
&semaphore,
)
}))
.await?;
if dotenvy::var("CLOUDFLARE_INTEGRATION")
.ok()
@@ -302,20 +151,6 @@ async fn main() -> Result<()> {
Ok(())
}
fn should_fetch(only_loader: Option<&str>, loader: &str) -> bool {
let Some(only_loader) = only_loader else {
return true;
};
only_loader.split(',').any(|entry| {
let entry = entry.trim();
entry.eq_ignore_ascii_case("all")
|| entry.eq_ignore_ascii_case(loader)
|| (loader == "neo" && entry.eq_ignore_ascii_case("neoforge"))
})
}
pub struct UploadFile {
file: bytes::Bytes,
content_type: Option<String>,
@@ -413,13 +248,11 @@ fn check_env_vars() -> bool {
failed |= check_var::<String>("BASE_URL");
if dotenvy::var("LOCAL_OUTPUT_DIR").is_err() {
failed |= check_var::<String>("S3_ACCESS_TOKEN");
failed |= check_var::<String>("S3_SECRET");
failed |= check_var::<String>("S3_URL");
failed |= check_var::<String>("S3_REGION");
failed |= check_var::<String>("S3_BUCKET_NAME");
}
failed |= check_var::<String>("S3_ACCESS_TOKEN");
failed |= check_var::<String>("S3_SECRET");
failed |= check_var::<String>("S3_URL");
failed |= check_var::<String>("S3_REGION");
failed |= check_var::<String>("S3_BUCKET_NAME");
if dotenvy::var("CLOUDFLARE_INTEGRATION")
.ok()
-145
View File
@@ -1,145 +0,0 @@
//! Determines how version info is generated for pairs of game and loader
//! versions.
//!
//! When a user installs a version of the game, they install two things: the
//! game (of some specific version), and a loader (of some other specific
//! version). Each combination of game and loader version requires a specific
//! configuration, like a specific set of libraries that must be downloaded and
//! run along with the game. However, some versions of the game or loader may
//! change configuration requirements without the other version being affected.
//! For example, pre-26.x game versions with Quilt require the Quilt `hashed`
//! libraries to also be downloaded. However, 26.x and later don't require the
//! `hashed` libraries, and don't even have a download for them. The problem is
//! that Quilt loader 0.30.0 can be used for both pre-26.x and 26.x - but our v0
//! manifest files can't differentiate the two. The result is that you either
//! break compatibility for 0.30.0 game versions pre-26.x, or break 0.30.0
//! on 26.x and later.
//!
//! To fix this, v1 introduces the concept of *version groups*: game versions
//! before 26.x are version group v1, and 26.x and later are v2. Then, we
//! parameterize our version info on both version group and loader version,
//! letting us specify the right configuration based on both game version and
//! loader version.
//!
//! Why not parameterize on game version and loader version directly? Most game
//! versions have the same configuration as their surrounding game versions, so
//! we'd end up with many duplicate configurations: the number of game versions
//! multiplied by the number of loader versions.
//!
//! This file lets you configure what game versions are grouped together.
//!
//! Each version group is templated from a specific game version - e.g. game
//! version 1.21 is used as the template file for 1.20, 1.19, etc.
pub const UNIVERSAL_METADATA_GROUP: &str = "universal";
pub const QUILT_LEGACY_METADATA_GROUP: &str = "v1";
pub const QUILT_MODERN_METADATA_GROUP: &str = "v2";
pub struct MetadataGroup {
pub id: &'static str,
/// Minecraft version used to fetch and template this group's loader profiles.
pub loader_profile_template_game_version: String,
pub game_versions: Vec<String>,
}
pub fn metadata_groups<'a>(
mod_loader: &str,
game_versions: impl IntoIterator<Item = &'a str>,
) -> Vec<MetadataGroup> {
// Non-Quilt loaders don't need the concept of version groups, so we just
// make one "universal" group, and template it on 1.21.
if mod_loader != "quilt" {
return vec![MetadataGroup {
id: UNIVERSAL_METADATA_GROUP,
loader_profile_template_game_version: "1.21".to_string(),
game_versions: game_versions
.into_iter()
.map(|x| x.to_string())
.collect(),
}];
}
let game_versions = game_versions.into_iter().collect::<Vec<_>>();
let legacy_game_versions = game_versions
.iter()
.copied()
.filter(|game_version| {
metadata_group_id_for_game_version(mod_loader, game_version)
== QUILT_LEGACY_METADATA_GROUP
})
.collect::<Vec<_>>();
let modern_game_versions = game_versions
.iter()
.copied()
.filter(|game_version| {
metadata_group_id_for_game_version(mod_loader, game_version)
== QUILT_MODERN_METADATA_GROUP
})
.collect::<Vec<_>>();
let mut groups = Vec::new();
if !legacy_game_versions.is_empty() {
groups.push(MetadataGroup {
id: QUILT_LEGACY_METADATA_GROUP,
loader_profile_template_game_version: legacy_game_versions
.iter()
.find(|x| **x == "1.21")
.copied()
.unwrap_or(legacy_game_versions[0])
.to_string(),
game_versions: legacy_game_versions
.iter()
.map(|x| x.to_string())
.collect(),
});
}
if !modern_game_versions.is_empty() {
groups.push(MetadataGroup {
id: QUILT_MODERN_METADATA_GROUP,
loader_profile_template_game_version: modern_game_versions[0]
.to_string(),
game_versions: modern_game_versions
.iter()
.map(|x| x.to_string())
.collect(),
});
}
groups
}
pub fn metadata_group_for_game_version<'a>(
groups: &'a [MetadataGroup],
mod_loader: &str,
game_version: &str,
) -> Option<&'a MetadataGroup> {
let group_id = metadata_group_id_for_game_version(mod_loader, game_version);
groups.iter().find(|group| group.id == group_id)
}
fn metadata_group_id_for_game_version(
mod_loader: &str,
game_version: &str,
) -> &'static str {
if mod_loader == "quilt" && is_modern_quilt_game_version(game_version) {
QUILT_MODERN_METADATA_GROUP
} else if mod_loader == "quilt" {
QUILT_LEGACY_METADATA_GROUP
} else {
UNIVERSAL_METADATA_GROUP
}
}
// Update these Quilt group boundaries if upstream loader profiles gain another
// structural incompatibility between Minecraft versions.
fn is_modern_quilt_game_version(game_version: &str) -> bool {
let major = game_version
.split(['.', 'w'])
.next()
.and_then(|x| x.parse::<usize>().ok());
major.is_some_and(|x| x >= 26)
}
+1 -85
View File
@@ -3,11 +3,7 @@ use bytes::Bytes;
use s3::creds::Credentials;
use s3::{Bucket, Region};
use serde::de::DeserializeOwned;
use std::path::PathBuf;
use std::sync::{
Arc, LazyLock,
atomic::{AtomicUsize, Ordering},
};
use std::sync::{Arc, LazyLock};
use tokio::sync::Semaphore;
static BUCKET: LazyLock<Bucket> = LazyLock::new(|| {
@@ -59,8 +55,6 @@ pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
.unwrap()
});
static DOWNLOADED_FILE_COUNT: AtomicUsize = AtomicUsize::new(0);
#[tracing::instrument(skip(bytes, semaphore))]
pub async fn upload_file_to_bucket(
path: String,
@@ -141,74 +135,6 @@ pub async fn upload_url_to_bucket(
Ok(())
}
pub async fn write_file_to_local_output(
path: &str,
bytes: Bytes,
) -> Result<(), Error> {
let output_path = local_output_path(path)?;
if let Some(parent) = output_path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
tokio::fs::write(output_path, bytes).await?;
Ok(())
}
pub async fn write_url_to_local_output_mirrors(
output_path: String,
mirrors: Vec<String>,
sha1: Option<String>,
semaphore: &Arc<Semaphore>,
) -> Result<(), Error> {
if mirrors.is_empty() {
return Err(ErrorKind::InvalidInput(
"No mirrors provided!".to_string(),
)
.into());
}
for (index, mirror) in mirrors.iter().enumerate() {
let result = write_url_to_local_output(
output_path.clone(),
mirror.clone(),
sha1.clone(),
semaphore,
)
.await;
if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) {
return result;
}
}
unreachable!()
}
async fn write_url_to_local_output(
path: String,
url: String,
sha1: Option<String>,
semaphore: &Arc<Semaphore>,
) -> Result<(), Error> {
let data = download_file(&url, sha1.as_deref(), semaphore).await?;
write_file_to_local_output(&path, data).await?;
Ok(())
}
fn local_output_path(path: &str) -> Result<PathBuf, Error> {
let output_dir = dotenvy::var("LOCAL_OUTPUT_DIR").map_err(|_| {
ErrorKind::InvalidInput(
"LOCAL_OUTPUT_DIR is required for local output".to_string(),
)
})?;
Ok(PathBuf::from(output_dir).join(path))
}
#[tracing::instrument(skip(bytes))]
pub async fn sha1_async(bytes: Bytes) -> Result<String, Error> {
let hash = tokio::task::spawn_blocking(move || {
@@ -256,16 +182,6 @@ pub async fn download_file(
}
}
let downloaded = DOWNLOADED_FILE_COUNT
.fetch_add(1, Ordering::Relaxed)
+ 1;
if downloaded.is_multiple_of(100) {
tracing::info!(
downloaded_files = downloaded,
"Downloaded metadata files"
);
}
return Ok(bytes);
} else if attempt <= RETRIES {
continue;
+1 -14
View File
@@ -414,7 +414,7 @@ components:
- unknown
- signature
example: required-resource-pack
# https://github.com/modrinth/code/blob/main/apps/labrinth/src/routes/v2/version_creation.rs#L32-L76
# https://github.com/modrinth/labrinth/blob/master/src/routes/version_creation.rs#L27-L57
CreatableVersion:
allOf:
- $ref: '#/components/schemas/BaseVersion'
@@ -446,11 +446,6 @@ components:
- client_or_server_prefers_both
- unknown
description: The environment that this version is for.
file_types:
type: object
additionalProperties:
$ref: '#/components/schemas/FileTypeEnum'
description: A map of file parts to their associated file type, a file type is used for additional files such as sources jars.
required:
- file_parts
- project_id
@@ -692,12 +687,6 @@ components:
type: string
description: The ID of the project
example: AABBCCDD
all_project_types:
type: array
items:
type: string
description: All project types across every version of the project, unlike `project_type` which only reflects a version-specific type
example: [mod, plugin, datapack]
author:
type: string
description: The username of the project's author
@@ -754,7 +743,6 @@ components:
- client_side
- server_side
- project_id
- all_project_types
- author
- versions
- follows
@@ -1955,7 +1943,6 @@ paths:
These are the most commonly used facet types:
- `project_type`
- `all_project_types` (matches against every project type across all of the project's versions, not just the primary/version-specific type)
- `categories` (loaders are lumped in with categories in search)
- `versions`
- `client_side`
@@ -41,38 +41,38 @@ export const VISIBLE_PROJECT_STATUS_CHANGE_EVENT_STATUS_SET =
export const LIGHT_LEGEND_PALETTE = [
'hsl(152, 100%, 34%)',
'hsl(41, 79%, 46%)',
'hsl(203, 76%, 64%)',
'hsl(0, 93%, 62%)',
'hsl(143, 66%, 29%)',
'hsl(58, 89%, 25%)',
'hsl(311, 64%, 49%)',
'hsl(198, 91%, 32%)',
'hsl(12, 88%, 27%)',
'hsl(236, 61%, 60%)',
'hsl(102, 59%, 74%)',
'hsl(293, 76%, 79%)',
'hsl(67, 99%, 41%)',
'hsl(179, 100%, 50%)',
'hsl(102, 100%, 61%)',
'hsl(0, 100%, 32%)',
'hsl(26, 100%, 42%)',
'hsl(202, 100%, 35%)',
'hsl(327, 45%, 64%)',
'hsl(41, 100%, 45%)',
'hsl(250, 60%, 33%)',
'hsl(170, 43%, 47%)',
'hsl(330, 60%, 33%)',
'hsl(46, 100%, 36%)',
'hsl(167, 100%, 30%)',
'hsl(343, 38%, 45%)',
'hsl(222, 100%, 28%)',
'hsl(270, 62%, 60%)',
'hsl(32, 100%, 37%)',
'hsl(349, 57%, 51%)',
'hsl(191, 43%, 37%)',
]
export const DARK_LEGEND_PALETTE = [
'hsl(145, 78%, 48%)',
'hsl(41, 79%, 46%)',
'hsl(203, 76%, 64%)',
'hsl(0, 93%, 62%)',
'hsl(143, 66%, 29%)',
'hsl(58, 94%, 45%)',
'hsl(311, 64%, 49%)',
'hsl(198, 91%, 32%)',
'hsl(12, 88%, 27%)',
'hsl(236, 61%, 60%)',
'hsl(102, 59%, 74%)',
'hsl(293, 76%, 79%)',
'hsl(61, 92%, 33%)',
'hsl(179, 100%, 50%)',
'hsl(102, 100%, 61%)',
'hsl(0, 100%, 32%)',
'hsl(41, 100%, 50%)',
'hsl(202, 77%, 63%)',
'hsl(323, 66%, 72%)',
'hsl(56, 85%, 60%)',
'hsl(255, 92%, 80%)',
'hsl(12, 100%, 67%)',
'hsl(176, 58%, 56%)',
'hsl(60, 100%, 41%)',
'hsl(165, 80%, 38%)',
'hsl(341, 36%, 56%)',
'hsl(226, 60%, 49%)',
'hsl(252, 53%, 62%)',
'hsl(75, 59%, 50%)',
'hsl(195, 56%, 42%)',
'hsl(30, 59%, 56%)',
]
@@ -288,7 +288,6 @@ type PaletteRankEntry = {
key: string
label: string
total: number
excludedFromRank?: boolean
}
function formatDatasetTooltip(projectName: string | undefined): string | undefined {
@@ -331,12 +330,9 @@ function buildPaletteColorsByDownloadRank(
const colorsByKey = new Map<string, string>()
if (palette.length === 0) return colorsByKey
const compareEntries = (a: PaletteRankEntry, b: PaletteRankEntry) =>
b.total - a.total || a.label.localeCompare(b.label) || a.key.localeCompare(b.key)
const rankedEntries = entries.filter((entry) => !entry.excludedFromRank).sort(compareEntries)
const excludedEntries = entries.filter((entry) => entry.excludedFromRank).sort(compareEntries)
const sortedEntries = [...rankedEntries, ...excludedEntries]
const sortedEntries = [...entries].sort(
(a, b) => b.total - a.total || a.label.localeCompare(b.label) || a.key.localeCompare(b.key),
)
sortedEntries.forEach((entry, index) => {
colorsByKey.set(entry.key, getPaletteColorForIndex(index, palette))
})
@@ -344,27 +340,6 @@ function buildPaletteColorsByDownloadRank(
return colorsByKey
}
function isExcludedFromPaletteRank(breakdownValues: readonly string[]): boolean {
return breakdownValues.some(
(value) =>
isUnknownAnalyticsBreakdownValue(value) || isNoDependentAnalyticsBreakdownValue(value),
)
}
function buildPaletteRankEntry(
key: string,
breakdownValues: readonly string[],
total: number,
formatLabel: (breakdownValues: readonly string[]) => string,
): PaletteRankEntry {
return {
key,
label: formatLabel(breakdownValues),
total,
excludedFromRank: isExcludedFromPaletteRank(breakdownValues),
}
}
export function getMetricValue(
point: Labrinth.Analytics.v3.ProjectAnalytics,
activeStat: AnalyticsDashboardStat,
@@ -524,14 +499,11 @@ export function buildChartDatasets(
})
const colorsByBreakdown = buildPaletteColorsByDownloadRank(
Array.from(dataByBreakdown.keys()).map((breakdownKey) =>
buildPaletteRankEntry(
breakdownKey,
breakdownValuesByKey.get(breakdownKey) ?? [],
downloadTotalsByBreakdown.get(breakdownKey) ?? 0,
formatChartBreakdownLabels,
),
),
Array.from(dataByBreakdown.keys()).map((breakdownKey) => ({
key: breakdownKey,
label: formatChartBreakdownLabels(breakdownValuesByKey.get(breakdownKey) ?? []),
total: downloadTotalsByBreakdown.get(breakdownKey) ?? 0,
})),
palette,
)
@@ -566,7 +538,7 @@ export function buildChartDatasets(
? isNoDependentAnalyticsBreakdownValue(dependentProjectId)
? formatMessage(analyticsMessages.noDependentTooltip)
: isUnknownAnalyticsBreakdownValue(dependentProjectId)
? formatMessage(analyticsMessages.unknownDependentTooltip)
? formatMessage(analyticsMessages.unknown)
: formatDependentProjectDatasetTooltip(
versionName,
dependentProjectName,
@@ -703,14 +675,11 @@ export function buildChartDatasets(
})
const colorsByBreakdown = buildPaletteColorsByDownloadRank(
Array.from(dataByProjectBreakdown.keys()).map((breakdownKey) =>
buildPaletteRankEntry(
breakdownKey,
breakdownValuesByKey.get(breakdownKey) ?? [],
downloadTotalsByProjectBreakdown.get(breakdownKey) ?? 0,
formatChartBreakdownLabels,
),
),
Array.from(dataByProjectBreakdown.keys()).map((breakdownKey) => ({
key: breakdownKey,
label: formatChartBreakdownLabels(breakdownValuesByKey.get(breakdownKey) ?? []),
total: downloadTotalsByProjectBreakdown.get(breakdownKey) ?? 0,
})),
palette,
)
@@ -92,11 +92,6 @@ export const analyticsMessages = defineMessages({
id: 'analytics.value.no-dependent-tooltip',
defaultMessage: 'Downloaded for reasons other than being a dependency',
},
unknownDependentTooltip: {
id: 'analytics.value.unknown-dependent-tooltip',
defaultMessage:
"There's no metadata to determine which dependent project this download attributes to.",
},
other: {
id: 'analytics.value.other',
defaultMessage: 'Other',
@@ -515,7 +515,7 @@ function getDependentProjectTooltip(row: AnalyticsTableRow) {
return formatMessage(analyticsMessages.noDependentTooltip)
}
if (isUnknownAnalyticsBreakdownValue(row.breakdownValues.dependent_project_download)) {
return formatMessage(analyticsMessages.unknownDependentTooltip)
return formatMessage(analyticsMessages.unknown)
}
const dependencyProjectIds = new Set(row.dependentOnProjectIds)
@@ -1,494 +0,0 @@
<template>
<div v-if="downloadRows.length > 0" class="flex flex-col gap-1">
<div v-if="showTitle" class="flex flex-wrap items-center justify-between gap-2">
<h3 class="m-0 flex items-center gap-1.5 text-base font-semibold text-contrast">
{{ sectionTitle }}
<InfoIcon
v-if="duplicateDependencyRowsHidden"
v-tooltip="formatMessage(messages.duplicateDependenciesHidden)"
aria-hidden="true"
class="size-4 text-secondary"
/>
</h3>
</div>
<div class="flex flex-col gap-2">
<DownloadDependency
v-for="dependency in downloadRows"
:key="dependency.key"
:dependency="dependency"
@download="emit('download')"
/>
</div>
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { FileIcon, InfoIcon } from '@modrinth/assets'
import {
type CdnDownloadReason,
defineMessages,
fileTypeMessages,
injectModrinthClient,
useVIntl,
} from '@modrinth/ui'
import type { DisplayProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import { type Component, computed, watch } from 'vue'
import DownloadDependency from './DownloadDependency.vue'
defineOptions({
name: 'DownloadDependencies',
})
type DownloadModalProject = Omit<Labrinth.Projects.v2.Project, 'project_type'> & {
project_type: DisplayProjectType
actualProjectType: Labrinth.Projects.v2.ProjectType
}
type ResolvedContent = Labrinth.Content.v3.ResolvedContent | Labrinth.Content.v3.SkippedContent
interface DownloadDependencyRow {
key: string
name: string
icon?: string
fallbackIcon?: Component
projectHref?: string
downloadHref?: string
filename?: string
fileSize?: number
typeLabel: string
unavailableTooltip: string
dependencies: DownloadDependencyRow[]
}
interface DownloadableDependencyFile {
href: string
filename: string
name: string
}
const props = withDefaults(
defineProps<{
dependencies?: DownloadDependencyRow[] | null
project?: DownloadModalProject | null
selectedVersion?: Labrinth.Versions.v3.Version | null
currentGameVersion?: string | null
currentPlatform?: string | null
downloadReason?: CdnDownloadReason
additionalFiles?: Labrinth.Versions.v3.VersionFile[]
showTitle?: boolean
}>(),
{
dependencies: null,
project: null,
selectedVersion: null,
currentGameVersion: null,
currentPlatform: null,
downloadReason: 'standalone',
additionalFiles: () => [],
showTitle: true,
},
)
const emit = defineEmits<{
download: []
'update:downloadable-files': [files: DownloadableDependencyFile[]]
}>()
const client = injectModrinthClient()
const { createProjectDownloadUrl } = useCdnDownloadContext()
const { formatMessage } = useVIntl()
const shouldResolveDependencies = computed(
() => !props.dependencies && !!props.project && !!props.selectedVersion,
)
const dependencyResolutionPreferences = computed<Labrinth.Content.v3.ResolutionPreferences>(() => ({
game_versions: props.selectedVersion?.game_versions || [],
loaders: props.currentPlatform ? [props.currentPlatform] : props.selectedVersion?.loaders || [],
}))
const { data: dependencyResolution } = useQuery({
queryKey: computed(() => [
'project-download-modal',
'content-resolve',
props.project?.id,
props.selectedVersion?.id,
props.project?.project_type,
dependencyResolutionPreferences.value,
]),
queryFn: () =>
client.labrinth.content_v3.resolve({
project_id: props.project!.id,
version_id: props.selectedVersion!.id,
content_type: resolveContentType(props.project!.project_type),
selected: dependencyResolutionPreferences.value,
target: dependencyResolutionPreferences.value,
}),
enabled: shouldResolveDependencies,
})
const visibleResolvedDependencies = computed<ResolvedContent[]>(() => {
return [
...(dependencyResolution.value?.dependencies || []),
...(dependencyResolution.value?.skipped || []),
].filter(shouldShowDependency)
})
const dependencyVersionIds = computed<string[]>(() => {
return [
...new Set(
visibleResolvedDependencies.value
.filter((dependency) => !('reason' in dependency))
.map((dependency) => dependency.version_id)
.filter((versionId): versionId is string => !!versionId),
),
]
})
const { data: dependencyVersions } = useQuery({
queryKey: computed(() => [
'project-download-modal',
'resolved-versions',
dependencyVersionIds.value,
]),
queryFn: () => client.labrinth.versions_v3.getVersions(dependencyVersionIds.value),
enabled: computed(() => shouldResolveDependencies.value && dependencyVersionIds.value.length > 0),
})
const dependencyVersionById = computed(() => {
const map = new Map<string, Labrinth.Versions.v3.Version>()
for (const version of dependencyVersions.value || []) {
if (!version) continue
map.set(version.id, version)
}
return map
})
const dependencyProjectIds = computed<string[]>(() => {
return [
...new Set(
visibleResolvedDependencies.value
.map((dependency) => dependency.project_id)
.filter((projectId): projectId is string => !!projectId),
),
]
})
const { data: dependencyProjects } = useQuery({
queryKey: computed(() => [
'project-download-modal',
'resolved-projects',
dependencyProjectIds.value,
]),
queryFn: () => client.labrinth.projects_v2.getMultiple(dependencyProjectIds.value),
enabled: computed(() => shouldResolveDependencies.value && dependencyProjectIds.value.length > 0),
})
const dependencyProjectById = computed(() => {
const map = new Map<string, Labrinth.Projects.v2.Project>()
for (const project of dependencyProjects.value || []) {
map.set(project.id, project)
}
return map
})
const dependenciesByParentVersionId = computed(() => {
const map = new Map<string, ResolvedContent[]>()
for (const dependency of visibleResolvedDependencies.value) {
if (!dependency.dependent_on_version_id) continue
const dependencies = map.get(dependency.dependent_on_version_id) || []
dependencies.push(dependency)
map.set(dependency.dependent_on_version_id, dependencies)
}
return map
})
const dependenciesLoaded = computed(() => {
if (!shouldResolveDependencies.value) return false
if (!dependencyResolution.value) return false
if (
dependencyResolution.value.primary.version_id &&
dependencyResolution.value.primary.version_id !== props.selectedVersion?.id
) {
return false
}
if (
!dependencyVersionIds.value.every((versionId) => dependencyVersionById.value.has(versionId))
) {
return false
}
if (
!dependencyProjectIds.value.every((projectId) => dependencyProjectById.value.has(projectId))
) {
return false
}
return true
})
const resolvedDependencyRows = computed<DownloadDependencyRow[]>(() => {
if (!dependenciesLoaded.value) return []
const primaryVersionId =
dependencyResolution.value?.primary.version_id || props.selectedVersion?.id
if (!primaryVersionId) return []
const dependencies = dependenciesByParentVersionId.value.get(primaryVersionId) || []
return dependencies.flatMap((dependency) => {
const row = createDependencyRow(dependency)
return row ? [row] : []
})
})
const dependencyRows = computed<DownloadDependencyRow[]>(
() => props.dependencies || resolvedDependencyRows.value,
)
const visibleDependencyRows = computed<DownloadDependencyRow[]>(() =>
dedupeDependencyRows(dependencyRows.value),
)
const duplicateDependencyRowsHidden = computed(() =>
hasDuplicateDependencyRows(dependencyRows.value),
)
const additionalFileRows = computed<DownloadDependencyRow[]>(() =>
props.additionalFiles.map((file) => ({
key: `additional-file-${additionalFileKey(file)}`,
name: file.filename,
fallbackIcon: FileIcon,
downloadHref: getDownloadUrl(file.url),
filename: file.filename,
fileSize: file.size,
typeLabel: fileTypeLabel(file.file_type),
unavailableTooltip: formatMessage(messages.unavailableFile),
dependencies: [],
})),
)
const downloadRows = computed<DownloadDependencyRow[]>(() => [
...visibleDependencyRows.value,
...additionalFileRows.value,
])
const sectionTitle = computed(() =>
formatMessage(
visibleDependencyRows.value.length > 0
? messages.dependenciesTitle
: messages.additionalFilesTitle,
),
)
const downloadableDependencyFiles = computed<DownloadableDependencyFile[]>(() =>
collectDownloadableDependencyFiles(visibleDependencyRows.value),
)
watch(
downloadableDependencyFiles,
(files) => {
emit('update:downloadable-files', files)
},
{ immediate: true },
)
function primaryFileForVersion(version?: Labrinth.Versions.v3.Version) {
return version?.files?.find((file) => file.primary) || version?.files?.[0]
}
function shouldShowDependency(dependency: ResolvedContent) {
return !(
'reason' in dependency && ['duplicate_project', 'quilt_fabric_api'].includes(dependency.reason)
)
}
function createDependencyRow(dependency: ResolvedContent): DownloadDependencyRow | null {
const versionId = dependency.version_id ?? undefined
const version = versionId ? dependencyVersionById.value.get(versionId) : undefined
const project = dependencyProjectById.value.get(dependency.project_id)
if (!project) return null
const primaryFile = primaryFileForVersion(version)
const unavailableTooltip =
'reason' in dependency && dependency.reason
? skippedReasonLabel(dependency.reason)
: formatMessage(messages.unavailableDependency)
const name = project.title
return {
key: `${dependency.project_id}-${versionId ?? 'unresolved'}-${
'reason' in dependency ? dependency.reason : 'resolved'
}`,
name,
icon: project.icon_url ?? undefined,
projectHref: `/${project.project_type}/${project.slug || project.id}`,
downloadHref:
'reason' in dependency || !primaryFile ? undefined : getDownloadUrl(primaryFile.url),
filename: primaryFile?.filename,
fileSize: primaryFile?.size,
typeLabel: 'Required',
unavailableTooltip,
dependencies: (versionId && dependenciesByParentVersionId.value.get(versionId)
? dependenciesByParentVersionId.value.get(versionId)!
: []
).flatMap((subDependency) => {
const row = createDependencyRow(subDependency)
return row ? [row] : []
}),
}
}
function skippedReasonLabel(reason: Labrinth.Content.v3.SkippedContent['reason']) {
return (
{
already_installed: formatMessage(messages.alreadyInstalledDependency),
duplicate_project: formatMessage(messages.duplicateDependency),
conflicting_dependency: formatMessage(messages.conflictingDependency),
no_compatible_version: formatMessage(messages.noCompatibleDependency),
missing_version: formatMessage(messages.missingDependencyVersion),
quilt_fabric_api: formatMessage(messages.quiltFabricApiDependency),
}[reason] || formatMessage(messages.unavailableDependency)
)
}
function resolveContentType(projectType: DisplayProjectType): Labrinth.Content.v3.ContentType {
return ['mod', 'plugin', 'datapack', 'resourcepack', 'shader', 'modpack'].includes(projectType)
? (projectType as Labrinth.Content.v3.ContentType)
: 'mod'
}
function getDownloadUrl(url: string) {
return createProjectDownloadUrl(url, {
reason: props.downloadReason,
gameVersion: props.currentGameVersion ?? undefined,
loader: props.currentPlatform ?? undefined,
})
}
function fileTypeLabel(type?: Labrinth.Versions.v3.FileType | null) {
return formatMessage(fileTypeMessages[type ?? 'unknown'] ?? fileTypeMessages.unknown)
}
function additionalFileKey(file: Labrinth.Versions.v3.VersionFile) {
return file.hashes?.sha1 ?? file.filename
}
function dedupeDependencyRows(
rows: DownloadDependencyRow[],
seenDependencies = new Set<string>(),
): DownloadDependencyRow[] {
return rows.flatMap((row) => {
const identity = dependencyRowIdentity(row)
if (seenDependencies.has(identity)) return []
seenDependencies.add(identity)
return [
{
...row,
dependencies: dedupeDependencyRows(row.dependencies, seenDependencies),
},
]
})
}
function dependencyRowIdentity(row: DownloadDependencyRow) {
return row.projectHref ?? row.downloadHref ?? row.key
}
function hasDuplicateDependencyRows(
rows: DownloadDependencyRow[],
seenDependencies = new Set<string>(),
): boolean {
for (const row of rows) {
const rowId = dependencyRowIdentity(row)
if (seenDependencies.has(rowId)) return true
seenDependencies.add(rowId)
if (hasDuplicateDependencyRows(row.dependencies, seenDependencies)) return true
}
return false
}
function collectDownloadableDependencyFiles(
rows: DownloadDependencyRow[],
seenHrefs = new Set<string>(),
): DownloadableDependencyFile[] {
const files: DownloadableDependencyFile[] = []
for (const row of rows) {
if (row.downloadHref && !seenHrefs.has(row.downloadHref)) {
seenHrefs.add(row.downloadHref)
files.push({
href: row.downloadHref,
filename: row.filename || filenameFromUrl(row.downloadHref),
name: row.name,
})
}
files.push(...collectDownloadableDependencyFiles(row.dependencies, seenHrefs))
}
return files
}
function filenameFromUrl(url: string) {
try {
const filename = new URL(url).pathname.split('/').pop()
return filename ? decodeURIComponent(filename) : 'dependency.jar'
} catch {
return 'dependency.jar'
}
}
const messages = defineMessages({
dependenciesTitle: {
id: 'project.download.dependencies-title',
defaultMessage: 'Dependencies',
},
duplicateDependenciesHidden: {
id: 'project.download.duplicate-dependencies-hidden',
defaultMessage: 'Duplicate dependencies are hidden',
},
additionalFilesTitle: {
id: 'project.download.additional-files-title',
defaultMessage: 'Additional files',
},
alreadyInstalledDependency: {
id: 'project.download.dependency-already-installed',
defaultMessage: 'This dependency is already installed',
},
conflictingDependency: {
id: 'project.download.dependency-conflicting',
defaultMessage: 'This dependency conflicts with another dependency',
},
duplicateDependency: {
id: 'project.download.dependency-duplicate',
defaultMessage: 'This dependency is already included',
},
missingDependencyVersion: {
id: 'project.download.dependency-missing-version',
defaultMessage: 'This dependency version is unavailable',
},
noCompatibleDependency: {
id: 'project.download.dependency-no-compatible-version',
defaultMessage: 'No compatible version is available for this dependency',
},
quiltFabricApiDependency: {
id: 'project.download.dependency-quilt-fabric-api',
defaultMessage: 'Fabric API is skipped for Quilt',
},
unavailableDependency: {
id: 'project.download.dependency-unavailable',
defaultMessage: 'This dependency cannot be downloaded',
},
unavailableFile: {
id: 'project.download.file-unavailable',
defaultMessage: 'This file cannot be downloaded',
},
})
</script>
@@ -1,149 +0,0 @@
<template>
<div class="flex min-w-0 flex-col gap-2">
<div
class="grid min-h-10 grid-cols-[minmax(0,1fr)_min-content] items-center gap-3 rounded-xl bg-button-bg py-0 pl-3.5 pr-2 text-primary"
>
<span class="flex min-w-0 items-center gap-3">
<Avatar
v-if="dependency.icon"
:src="dependency.icon"
:alt="dependency.name"
size="24px"
class="!rounded-lg !shadow-none"
/>
<span
v-else
class="flex size-4 flex-shrink-0 items-center justify-center rounded-lg border border-solid border-surface-5 text-secondary"
>
<component
:is="dependency.fallbackIcon ?? PackageIcon"
aria-hidden="true"
class="size-5"
/>
</span>
<a
v-if="dependency.projectHref"
ref="dependencyNameRef"
v-tooltip="truncatedTooltip(dependencyNameRef, dependency.name)"
:href="dependency.projectHref"
target="_blank"
rel="noopener noreferrer"
class="min-w-0 truncate text-base font-semibold text-contrast no-underline hover:underline"
>
{{ dependency.name }}
</a>
<span
v-else
ref="dependencyNameRef"
v-tooltip="truncatedTooltip(dependencyNameRef, dependency.name)"
class="min-w-0 truncate text-base font-semibold text-contrast"
>
{{ dependency.name }}
</span>
<TagItem class="shrink-0 border !border-solid border-surface-5 !px-3 !py-1 text-base">
{{ dependency.typeLabel }}
</TagItem>
</span>
<ButtonStyled v-if="dependency.downloadHref" circular type="transparent">
<a
v-tooltip="downloadTooltip"
:href="dependency.downloadHref"
:download="dependency.filename"
:aria-label="downloadTooltip"
@click="emit('download')"
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</a>
</ButtonStyled>
<ButtonStyled v-else circular type="transparent">
<button
v-tooltip="dependency.unavailableTooltip"
disabled
:aria-label="dependency.unavailableTooltip"
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</button>
</ButtonStyled>
</div>
<div
v-for="childDependency in dependency.dependencies"
:key="childDependency.key"
class="group/dependency relative pl-10"
>
<DownloadDependency :dependency="childDependency" class="z-1" @download="emit('download')" />
<div
aria-hidden="true"
class="absolute -top-2 left-6 z-0 h-[calc(100%+1rem)] w-0.5 bg-surface-5 group-first/dependency:-top-2 group-first/dependency:h-20 group-last/dependency:h-7"
/>
<div aria-hidden="true" class="absolute left-6 top-5 z-0 h-0.5 w-4 bg-surface-5" />
</div>
</div>
</template>
<script setup lang="ts">
import { DownloadIcon, PackageIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
defineMessages,
TagItem,
truncatedTooltip,
useFormatBytes,
useVIntl,
} from '@modrinth/ui'
import { type Component, computed, ref } from 'vue'
defineOptions({
name: 'DownloadDependency',
})
interface DownloadDependencyRow {
key: string
name: string
icon?: string
fallbackIcon?: Component
projectHref?: string
downloadHref?: string
filename?: string
fileSize?: number
typeLabel: string
unavailableTooltip: string
dependencies: DownloadDependencyRow[]
}
const props = defineProps<{
dependency: DownloadDependencyRow
}>()
const emit = defineEmits<{
download: []
}>()
const { formatMessage } = useVIntl()
const formatBytes = useFormatBytes()
const dependencyNameRef = ref<HTMLElement | null>(null)
const downloadTooltip = computed(() => {
const filename = props.dependency.filename || props.dependency.name
if (typeof props.dependency.fileSize === 'number') {
return formatMessage(messages.downloadFileWithSize, {
filename,
size: formatBytes(props.dependency.fileSize, 1),
})
}
return formatMessage(messages.downloadFile, { filename })
})
const messages = defineMessages({
downloadFile: {
id: 'project.download.dependency-download-file',
defaultMessage: 'Download {filename}',
},
downloadFileWithSize: {
id: 'project.download.dependency-download-file-with-size',
defaultMessage: 'Download {filename} ({size})',
},
})
</script>
@@ -1,766 +0,0 @@
<template>
<div class="flex w-full gap-2 max-sm:flex-wrap">
<Combobox
:model-value="currentGameVersion || undefined"
class="w-full"
:options="gameVersionOptions"
:placeholder="formatMessage(messages.selectGameVersion)"
:searchable="project.game_versions.length > 4"
search-autocomplete="off"
:search-placeholder="formatMessage(messages.searchGameVersions)"
:no-options-message="formatMessage(messages.noGameVersionsFound)"
trigger-class="!rounded-xl !bg-button-bg !px-3 !py-2"
dropdown-class="!rounded-xl"
select-search-text-on-focus
@update:model-value="selectGameVersion"
@search-input="versionFilter = $event"
@close="versionFilter = ''"
>
<template #option="{ item, isSelected }">
<div
v-tooltip="gameVersionOptionTooltip(item.value)"
class="flex w-full items-center justify-between gap-2 px-4 py-2"
:class="{
'text-brand-red opacity-40': isGameVersionUnavailable(item.value),
'text-green': isSelected,
'!opacity-100': isSelected,
'text-primary': !isGameVersionUnavailable(item.value) && !isSelected,
}"
>
<span class="min-w-0 truncate font-semibold leading-tight">{{ item.label }}</span>
<TriangleAlertIcon
v-if="isSelected && isGameVersionUnavailable(item.value)"
aria-hidden="true"
class="size-5 shrink-0 text-orange"
/>
</div>
</template>
<template #dropdown-footer>
<div
v-if="showVersionsCheckbox"
class="border-0 border-t border-solid border-surface-5 p-3"
>
<Checkbox
v-model="showAllVersionsModel"
:label="formatMessage(messages.showAllVersions)"
:disabled="!!versionFilter"
/>
</div>
</template>
</Combobox>
<Combobox
v-if="project.project_type !== 'resourcepack'"
:model-value="currentPlatform || undefined"
class="w-full"
:options="platformOptions"
:placeholder="formatMessage(messages.selectPlatform)"
trigger-class="!rounded-xl !bg-button-bg !px-3 !py-2"
dropdown-class="!rounded-xl"
@update:model-value="selectPlatform"
>
<template #option="{ item, isSelected }">
<div
v-tooltip="platformOptionTooltip(item.value, item.label)"
class="flex w-full items-center justify-between gap-2 px-4 py-2"
:class="{
'text-brand-red opacity-40': isPlatformUnavailable(item.value),
'text-green': isSelected,
'!opacity-100': isSelected,
'text-primary': !isPlatformUnavailable(item.value) && !isSelected,
}"
>
<span class="min-w-0 truncate font-semibold leading-tight">{{ item.label }}</span>
<TriangleAlertIcon
v-if="isSelected && isPlatformUnavailable(item.value)"
aria-hidden="true"
class="size-5 shrink-0 text-orange"
/>
</div>
</template>
</Combobox>
</div>
<div v-if="selectedVersion" class="flex flex-col gap-1">
<div class="flex flex-wrap items-center justify-between gap-2">
<h3 class="relative top-0.5 m-0 text-base font-semibold text-contrast">
{{ formatMessage(messages.compatibleVersionTitle) }}
</h3>
<ButtonStyled v-if="downloadAllFiles.length > 1" type="transparent">
<button :disabled="downloadingSelectedVersion" @click="downloadSelectedVersionFiles">
<SpinnerIcon v-if="downloadingSelectedVersion" aria-hidden="true" class="animate-spin" />
<DownloadIcon v-else aria-hidden="true" />
{{
formatMessage(
downloadingSelectedVersion
? messages.downloadingSelectedVersion
: messages.downloadAllSelectedVersion,
{
current: selectedVersionDownloadProgress.current,
total: selectedVersionDownloadProgress.total,
},
)
}}
</button>
</ButtonStyled>
</div>
<div
class="grid grid-cols-[1fr_min-content] items-center gap-3 rounded-2xl bg-surface-2 px-3 py-3"
>
<div class="flex min-w-0 flex-col gap-1">
<div class="flex min-w-0 items-center gap-2">
<nuxt-link
v-tooltip="truncatedTooltip(versionNumberRef, selectedVersion.version_number)"
:to="`/${project.project_type}/${project.slug || project.id}/version/${selectedVersion.id}`"
target="_blank"
rel="noopener noreferrer"
class="block min-w-0 text-contrast no-underline hover:underline"
>
<span ref="versionNumberRef" class="block truncate font-semibold">
{{ selectedVersion.version_number }}
</span>
</nuxt-link>
<VersionChannelTag
:channel="selectedVersion.version_type"
class="relative -top-px !py-0.5"
/>
</div>
<p
ref="versionNameRef"
v-tooltip="truncatedTooltip(versionNameRef, selectedVersion.name)"
class="m-0 w-fit max-w-full truncate text-sm text-secondary"
>
{{ selectedVersion.name }}
</p>
</div>
<ButtonStyled v-if="selectedPrimaryFile" color="brand" circular>
<a
v-tooltip="'Download'"
:href="selectedPrimaryFileDownloadUrl"
:download="selectedPrimaryFile.filename"
:aria-label="
formatMessage(messages.downloadVersion, {
version: selectedVersion.version_number,
})
"
@click="emit('download')"
>
<DownloadIcon aria-hidden="true" />
</a>
</ButtonStyled>
</div>
</div>
<p v-else-if="currentPlatform && currentGameVersion && versions.length > 0">
{{
formatMessage(messages.noVersionsAvailable, {
gameVersion: currentGameVersion,
platform: currentPlatformText,
})
}}
</p>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, SpinnerIcon, TriangleAlertIcon } from '@modrinth/assets'
import {
ButtonStyled,
type CdnDownloadReason,
Checkbox,
Combobox,
type ComboboxOption,
defineMessages,
getTagMessage,
injectNotificationManager,
truncatedTooltip,
useDebugLogger,
useVIntl,
} from '@modrinth/ui'
import VersionChannelTag from '@modrinth/ui/src/components/version/VersionChannelTag.vue'
import type { DisplayProjectType } from '@modrinth/utils'
import dayjs from 'dayjs'
import JSZip from 'jszip'
import { computed, ref, watch } from 'vue'
defineOptions({
name: 'DownloadProject',
})
type DownloadModalProject = Omit<Labrinth.Projects.v2.Project, 'project_type'> & {
project_type: DisplayProjectType
actualProjectType: Labrinth.Projects.v2.ProjectType
}
type ProjectDownloadSelection = {
currentGameVersion: string | null
currentPlatform: string | null
selectedVersion: Labrinth.Versions.v3.Version | null
selectedPrimaryFile: Labrinth.Versions.v3.VersionFile | null
}
type DownloadableFile = {
href: string
filename: string
}
const props = withDefaults(
defineProps<{
project: DownloadModalProject
versions?: Labrinth.Versions.v3.Version[]
dependencyDownloadFiles?: DownloadableFile[]
downloadReason?: CdnDownloadReason
initialGameVersion?: string | null
initialPlatform?: string | null
incompatibleGameVersions?: string[]
incompatibleLoaders?: string[]
resetKey?: number
}>(),
{
versions: () => [],
dependencyDownloadFiles: () => [],
downloadReason: 'standalone',
initialGameVersion: null,
initialPlatform: null,
incompatibleGameVersions: () => [],
incompatibleLoaders: () => [],
resetKey: 0,
},
)
const emit = defineEmits<{
download: []
selectGameVersion: [gameVersion: string]
selectPlatform: [platform: string]
'update:selection': [selection: ProjectDownloadSelection]
}>()
const { formatMessage } = useVIntl()
const { createProjectDownloadUrl } = useCdnDownloadContext()
const { addNotification } = injectNotificationManager()
const debug = useDebugLogger('DownloadProject')
const tags = useGeneratedState()
const userSelectedGameVersion = ref<string | null>(props.initialGameVersion)
const userSelectedPlatform = ref<string | null>(props.initialPlatform)
const showAllVersions = ref(defaultShowAllVersions())
const versionFilter = ref('')
const versionNumberRef = ref<HTMLElement | null>(null)
const versionNameRef = ref<HTMLElement | null>(null)
const downloadingSelectedVersion = ref(false)
const selectedVersionDownloadProgress = ref({
current: 0,
total: 0,
})
const incompatibleGameVersionsSet = computed(() => new Set(props.incompatibleGameVersions))
const incompatibleLoadersSet = computed(() => new Set(props.incompatibleLoaders))
const showAllVersionsModel = computed({
get() {
return showAllVersions.value
},
set(value) {
showAllVersions.value = value
},
})
const selectedPlatform = computed<string | null>(() => {
if (userSelectedPlatform.value) return userSelectedPlatform.value
return props.project.loaders.length === 1 ? props.project.loaders[0] : null
})
const selectedGameVersion = computed<string | null>(() => {
if (userSelectedGameVersion.value) return userSelectedGameVersion.value
return props.project.game_versions.length === 1 ? props.project.game_versions[0] : null
})
const compatiblePlatforms = computed<string[]>(() => {
return props.project.loaders.filter(
(platform) =>
props.versions.some(
(version) =>
version.loaders.includes(platform) &&
(!selectedGameVersion.value || version.game_versions.includes(selectedGameVersion.value)),
) && !incompatibleLoadersSet.value.has(platform),
)
})
const currentPlatform = computed<string | null>(() => {
if (selectedPlatform.value) return selectedPlatform.value
return compatiblePlatforms.value.length === 1 ? compatiblePlatforms.value[0] : null
})
const possibleGameVersions = computed<string[]>(() => {
return props.versions
.filter((x) => !currentPlatform.value || x.loaders.includes(currentPlatform.value))
.flatMap((x) => x.game_versions)
})
const compatibleGameVersions = computed<string[]>(() => {
return props.project.game_versions.filter(
(gameVersion) =>
possibleGameVersions.value.includes(gameVersion) &&
!incompatibleGameVersionsSet.value.has(gameVersion),
)
})
const currentGameVersion = computed<string | null>(() => {
if (selectedGameVersion.value) return selectedGameVersion.value
return compatibleGameVersions.value.length === 1 ? compatibleGameVersions.value[0] : null
})
const possiblePlatforms = computed<string[]>(() => {
return props.versions
.filter((x) => !currentGameVersion.value || x.game_versions.includes(currentGameVersion.value))
.flatMap((x) => x.loaders)
})
const currentPlatformText = computed(() => {
if (!currentPlatform.value) return ''
return loaderLabel(currentPlatform.value)
})
const releaseVersions = computed<Set<string>>(() => {
const set = new Set<string>()
for (const gameVersion of tags.value.gameVersions || []) {
if (gameVersion?.version && gameVersion.version_type === 'release') {
set.add(gameVersion.version)
}
}
return set
})
const nonReleaseVersions = computed<Set<string>>(() => {
const set = new Set<string>()
for (const gameVersion of tags.value.gameVersions || []) {
if (gameVersion?.version && gameVersion.version_type !== 'release') {
set.add(gameVersion.version)
}
}
return set
})
const showVersionsCheckbox = computed(() => {
let hasRelease = false
let hasNonRelease = false
for (const version of props.project.game_versions) {
if (isReleaseGameVersion(version)) {
hasRelease = true
} else {
hasNonRelease = true
}
if (hasRelease && hasNonRelease) return true
}
return false
})
const filteredGameVersions = computed(() => {
return props.project.game_versions
.filter(
(x) =>
(versionFilter.value && x.includes(versionFilter.value)) ||
(!versionFilter.value && (showAllVersions.value || isReleaseGameVersion(x))),
)
.slice()
.reverse()
})
const gameVersionOptions = computed<ComboboxOption<string>[]>(() => {
return filteredGameVersions.value.map((gameVersion) => ({
value: gameVersion,
label: gameVersion,
class: '!px-0 !py-1',
}))
})
const platformOptions = computed<ComboboxOption<string>[]>(() => {
return props.project.loaders
.slice()
.reverse()
.map((platform) => ({
value: platform,
label: loaderLabel(platform),
class: '!px-0 !py-1',
}))
})
const filteredVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
const gameVersion = currentGameVersion.value
if (!gameVersion) return []
const platform = currentPlatform.value
const result = props.versions.filter((x) => {
const matchesPlatform =
props.project.project_type === 'resourcepack' || (!!platform && x.loaders.includes(platform))
return x.game_versions.includes(gameVersion) && matchesPlatform
})
debug('filteredVersions', {
total: props.versions.length,
filtered: result.length,
currentGameVersion: currentGameVersion.value,
currentPlatform: currentPlatform.value,
sampleLoaders: props.versions.slice(0, 3).map((v) => v.loaders),
})
return result
})
const filteredRelease = computed<Labrinth.Versions.v3.Version | undefined>(() => {
return filteredVersions.value.find((x) => x.version_type === 'release')
})
const filteredBeta = computed<Labrinth.Versions.v3.Version | undefined>(() => {
return filteredVersions.value.find(
(x) =>
x.version_type === 'beta' &&
(!filteredRelease.value ||
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))),
)
})
const filteredAlpha = computed<Labrinth.Versions.v3.Version | undefined>(() => {
return filteredVersions.value.find(
(x) =>
x.version_type === 'alpha' &&
(!filteredRelease.value ||
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))) &&
(!filteredBeta.value ||
dayjs(x.date_published).isAfter(dayjs(filteredBeta.value.date_published))),
)
})
const selectedVersion = computed<Labrinth.Versions.v3.Version | null>(() => {
return filteredRelease.value || filteredBeta.value || filteredAlpha.value || null
})
const selectedPrimaryFile = computed<Labrinth.Versions.v3.VersionFile | null>(() => {
return (
selectedVersion.value?.files?.find((file) => file.primary) ||
selectedVersion.value?.files?.[0] ||
null
)
})
const selectedPrimaryFileDownloadUrl = computed(() => {
if (!selectedPrimaryFile.value) return '#'
return getDownloadUrl(selectedPrimaryFile.value.url)
})
const selectedVersionDownloadFiles = computed(() => {
if (!selectedVersion.value) return []
return selectedVersion.value.files.map((file) => ({
href: getDownloadUrl(file.url),
filename: file.filename,
}))
})
const downloadAllFiles = computed(() => {
const files: DownloadableFile[] = []
const hrefs = new Set<string>()
for (const file of [...selectedVersionDownloadFiles.value, ...props.dependencyDownloadFiles]) {
if (hrefs.has(file.href)) continue
hrefs.add(file.href)
files.push(file)
}
return files
})
const selectedVersionZipFilename = computed(() => {
if (!selectedVersion.value) return `${sanitizeFilename(props.project.title)}.zip`
return `${sanitizeFilename(props.project.title)} ${sanitizeFilename(
selectedVersion.value.version_number,
)}.zip`
})
watch(
[currentGameVersion, currentPlatform, selectedVersion, selectedPrimaryFile],
() => {
emit('update:selection', {
currentGameVersion: currentGameVersion.value,
currentPlatform: currentPlatform.value,
selectedVersion: selectedVersion.value,
selectedPrimaryFile: selectedPrimaryFile.value,
})
},
{ immediate: true },
)
watch(
() => props.resetKey,
() => {
userSelectedGameVersion.value = props.initialGameVersion
userSelectedPlatform.value = props.initialPlatform
showAllVersions.value = defaultShowAllVersions()
versionFilter.value = ''
},
)
function selectGameVersion(gameVersion?: string) {
if (!gameVersion) return
userSelectedGameVersion.value = gameVersion
emit('selectGameVersion', gameVersion)
selectOnlyCompatiblePlatform()
}
function selectPlatform(platform?: string) {
if (!platform) return
userSelectedPlatform.value = platform
emit('selectPlatform', platform)
}
function getDownloadUrl(url: string) {
return createProjectDownloadUrl(url, {
reason: props.downloadReason,
gameVersion: currentGameVersion.value ?? undefined,
loader: currentPlatform.value ?? undefined,
})
}
async function downloadSelectedVersionFiles() {
if (downloadingSelectedVersion.value || downloadAllFiles.value.length <= 1) return
downloadingSelectedVersion.value = true
const files = [...downloadAllFiles.value]
selectedVersionDownloadProgress.value = {
current: 0,
total: files.length,
}
try {
const zip = new JSZip()
const usedFilenames = new Set<string>()
for (const [index, file] of files.entries()) {
selectedVersionDownloadProgress.value = {
current: index + 1,
total: files.length,
}
const response = await fetch(file.href)
if (!response.ok) {
throw new Error(`Failed to download ${file.filename}`)
}
zip.file(uniqueFilename(file.filename, usedFilenames), await response.blob())
}
downloadBlob(
await zip.generateAsync({
type: 'blob',
mimeType: 'application/zip',
}),
selectedVersionZipFilename.value,
)
emit('download')
} catch (error) {
console.error('Failed to download selected version files:', error)
addNotification({
title: formatMessage(messages.downloadSelectedVersionFailedTitle),
text: formatMessage(messages.downloadSelectedVersionFailedText),
type: 'error',
})
} finally {
downloadingSelectedVersion.value = false
selectedVersionDownloadProgress.value = {
current: 0,
total: 0,
}
}
}
function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
link.remove()
window.setTimeout(() => URL.revokeObjectURL(url), 0)
}
function sanitizeFilename(value: string) {
const sanitized = value
.replace(/[<>:"/\\|?*]/g, '')
.replace(/\s+/g, ' ')
.trim()
return sanitized || 'download'
}
function uniqueFilename(filename: string, usedFilenames: Set<string>) {
const sanitizedFilename = sanitizeFilename(filename)
if (!usedFilenames.has(sanitizedFilename)) {
usedFilenames.add(sanitizedFilename)
return sanitizedFilename
}
const extensionIndex = sanitizedFilename.lastIndexOf('.')
const basename =
extensionIndex > 0 ? sanitizedFilename.slice(0, extensionIndex) : sanitizedFilename
const extension = extensionIndex > 0 ? sanitizedFilename.slice(extensionIndex) : ''
let index = 2
let candidate = `${basename} (${index})${extension}`
while (usedFilenames.has(candidate)) {
index += 1
candidate = `${basename} (${index})${extension}`
}
usedFilenames.add(candidate)
return candidate
}
function loaderLabel(loader: string) {
return formatMessage(getTagMessage(loader, 'loader') ?? messages.unknownLoader)
}
function isReleaseGameVersion(version: string) {
if (releaseVersions.value.has(version)) return true
if (nonReleaseVersions.value.has(version)) return false
return true
}
function defaultShowAllVersions() {
return (
props.project.game_versions.length > 0 &&
props.project.game_versions.every((projectVersion) => {
const gameVersion = tags.value.gameVersions?.find((x) => x.version === projectVersion)
return !!gameVersion?.version_type && gameVersion.version_type !== 'release'
})
)
}
function isGameVersionUnavailable(gameVersion: string) {
return (
incompatibleGameVersionsSet.value.has(gameVersion) ||
!possibleGameVersions.value.includes(gameVersion)
)
}
function isPlatformUnavailable(platform: string) {
return incompatibleLoadersSet.value.has(platform) || !possiblePlatforms.value.includes(platform)
}
function selectOnlyCompatiblePlatform() {
const compatiblePlatforms = props.project.loaders.filter(
(platform) =>
possiblePlatforms.value.includes(platform) && !incompatibleLoadersSet.value.has(platform),
)
if (compatiblePlatforms.length !== 1) return
userSelectedPlatform.value = compatiblePlatforms[0]
emit('selectPlatform', compatiblePlatforms[0])
}
function gameVersionOptionTooltip(gameVersion: string) {
if (incompatibleGameVersionsSet.value.has(gameVersion)) {
return formatMessage(messages.baseGameVersionIncompatibleTooltip)
}
if (!possibleGameVersions.value.includes(gameVersion)) {
return formatMessage(messages.gameVersionUnsupportedTooltip, {
title: props.project.title,
gameVersion,
platform: currentPlatformText.value,
})
}
return null
}
function platformOptionTooltip(platform: string, platformLabel: string) {
if (incompatibleLoadersSet.value.has(platform)) {
return formatMessage(messages.baseLoaderIncompatibleTooltip)
}
if (!possiblePlatforms.value.includes(platform)) {
return formatMessage(messages.platformUnsupportedTooltip, {
title: props.project.title,
platform: platformLabel,
gameVersion: currentGameVersion.value,
})
}
return null
}
const messages = defineMessages({
baseGameVersionIncompatibleTooltip: {
id: 'project.download.base-game-version-incompatible-tooltip',
defaultMessage: 'This game version is incompatible with the base project.',
},
baseLoaderIncompatibleTooltip: {
id: 'project.download.base-loader-incompatible-tooltip',
defaultMessage: 'This loader is incompatible with the base project.',
},
gameVersionUnsupportedTooltip: {
id: 'project.download.game-version-unsupported-tooltip',
defaultMessage: '{title} does not support {gameVersion} for {platform}',
},
downloadVersion: {
id: 'project.download.download-version',
defaultMessage: 'Download {version}',
},
compatibleVersionTitle: {
id: 'project.download.compatible-version-title',
defaultMessage: 'Compatible version',
},
downloadAllSelectedVersion: {
id: 'project.download.selected-version-download-all',
defaultMessage: 'Download all (.zip)',
},
downloadingSelectedVersion: {
id: 'project.download.selected-version-downloading',
defaultMessage: 'Downloading... ({current}/{total})',
},
downloadSelectedVersionFailedTitle: {
id: 'project.download.selected-version-failed-title',
defaultMessage: 'Could not download version',
},
downloadSelectedVersionFailedText: {
id: 'project.download.selected-version-failed-text',
defaultMessage: 'One or more version files could not be downloaded. Please try again.',
},
noGameVersionsFound: {
id: 'project.download.no-game-versions-found',
defaultMessage: 'No game versions found',
},
noVersionsAvailable: {
id: 'project.download.no-versions-available',
defaultMessage: 'No versions available for {gameVersion} and {platform}.',
},
platformUnsupportedTooltip: {
id: 'project.download.platform-unsupported-tooltip',
defaultMessage: '{title} does not support {platform} for {gameVersion}',
},
searchGameVersions: {
id: 'project.download.search-game-versions',
defaultMessage: 'Select game version',
},
selectGameVersion: {
id: 'project.download.select-game-version',
defaultMessage: 'Select game version',
},
selectPlatform: {
id: 'project.download.select-platform',
defaultMessage: 'Select platform',
},
showAllVersions: {
id: 'project.download.show-all-versions',
defaultMessage: 'Show all versions',
},
unknownLoader: {
id: 'project.download.unknown-loader',
defaultMessage: 'Unknown loader',
},
})
</script>
@@ -1,111 +0,0 @@
<template>
<div
v-if="
project.project_type !== 'plugin' ||
project.loaders.some((x) => !tags.loaderData.allPluginLoaders.includes(x))
"
class="modrinth-app-section contents"
>
<div class="flex flex-col">
<a
class="modrinth-app-install-card flex items-center justify-between gap-3 rounded-2xl border border-solid border-brand-highlight bg-surface-1 px-4 py-3 text-primary no-underline transition-[filter] hover:brightness-110"
:href="`modrinth://mod/${project.slug}`"
@click="installWithApp"
>
<span class="flex w-full min-w-0 flex-col gap-1">
<div class="flex items-center justify-between">
<span class="flex min-w-0 items-center gap-1.5 font-medium text-contrast">
Install with
<span class="text-brand">Modrinth App</span>
<ModrinthIcon aria-hidden="true" class="size-4 flex-shrink-0 text-brand" />
</span>
<ExternalIcon
aria-hidden="true"
class="size-4 flex-shrink-0 text-contrast transition-colors"
/>
</div>
<span class="truncate text-base text-contrast opacity-80">
{{ formatMessage(messages.installWithModrinthAppDescription) }}
</span>
</span>
</a>
<Accordion ref="getModrinthAppAccordion">
<nuxt-link class="mt-2 flex justify-center text-brand-blue hover:underline" to="/app">
{{ formatMessage(messages.dontHaveModrinthApp) }}
</nuxt-link>
</Accordion>
</div>
<div class="flex items-center gap-4">
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
<span class="flex-shrink-0 text-sm font-medium text-secondary">
{{ formatMessage(messages.downloadManually) }}
</span>
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { ExternalIcon, ModrinthIcon } from '@modrinth/assets'
import { defineMessages, useVIntl } from '@modrinth/ui'
import type { DisplayProjectType } from '@modrinth/utils'
import { ref } from 'vue'
import Accordion from '~/components/ui/Accordion.vue'
defineOptions({
name: 'InstallWithModrinthApp',
})
type DownloadModalProject = Omit<Labrinth.Projects.v2.Project, 'project_type'> & {
project_type: DisplayProjectType
actualProjectType: Labrinth.Projects.v2.ProjectType
}
defineProps<{
project: DownloadModalProject
}>()
const { formatMessage } = useVIntl()
const tags = useGeneratedState()
const getModrinthAppAccordion = ref<InstanceType<typeof Accordion> | null>(null)
const messages = defineMessages({
dontHaveModrinthApp: {
id: 'project.download.no-app',
defaultMessage: "Don't have Modrinth App?",
},
downloadManually: {
id: 'project.download.manually',
defaultMessage: 'Download manually',
},
installWithModrinthAppDescription: {
id: 'project.download.install-with-app-description',
defaultMessage: 'Automatically install the correct version and dependencies.',
},
})
function installWithApp() {
setTimeout(() => {
getModrinthAppAccordion.value?.open()
}, 1500)
}
</script>
<style lang="scss" scoped>
.modrinth-app-install-card {
background: radial-gradient(
ellipse 90% 250% at 50% 200%,
color-mix(in srgb, var(--color-brand-shadow) 50%, var(--surface-1)) -30%,
var(--surface-1) 72%
);
}
@media (hover: none) and (max-width: 767px) {
.modrinth-app-section {
display: none;
}
}
</style>
@@ -1,447 +0,0 @@
<template>
<NewModal ref="modal" :on-show="onShow" :on-hide="onHide" width="544px">
<template #title>
<template v-if="project">
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" />
<div
ref="downloadTitleRef"
v-tooltip="truncatedTooltip(downloadTitleRef, downloadTitle)"
class="truncate text-lg font-extrabold text-contrast"
>
{{ downloadTitle }}
</div>
</template>
</template>
<template #default>
<div v-if="project" class="mx-auto flex w-full flex-col gap-4">
<InstallWithModrinthApp :project="project" />
<DownloadProject
:project="project"
:versions="versions"
:dependency-download-files="dependencyDownloadFiles"
:download-reason="downloadReason"
:initial-game-version="initialGameVersion"
:initial-platform="initialPlatform"
:incompatible-game-versions="showOptions.incompatibleGameVersions"
:incompatible-loaders="showOptions.incompatibleLoaders"
:reset-key="downloadProjectResetKey"
@select-game-version="selectGameVersion"
@select-platform="selectPlatform"
@update:selection="projectDownloadSelection = $event"
@download="onDownload"
/>
<div class="flex flex-col gap-4">
<DownloadDependencies
:project="project"
:selected-version="selectedVersion"
:current-game-version="currentGameVersion"
:current-platform="currentPlatform"
:download-reason="downloadReason"
:additional-files="additionalFiles"
@update:downloadable-files="dependencyDownloadFiles = $event"
@download="onDownload"
/>
</div>
<ServersPromo
v-if="flags.showProjectPageDownloadModalServersPromo"
:link="`/hosting#plan`"
@close="
() => {
flags.showProjectPageDownloadModalServersPromo = false
saveFeatureFlags()
}
"
/>
</div>
</template>
</NewModal>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
Avatar,
type CdnDownloadReason,
defineMessages,
injectModrinthClient,
NewModal,
ServersPromo,
truncatedTooltip,
useDebugLogger,
useVIntl,
} from '@modrinth/ui'
import type { DisplayProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
import { navigateTo } from '#app'
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
import DownloadDependencies from './DownloadDependencies.vue'
import DownloadProject from './DownloadProject.vue'
import InstallWithModrinthApp from './InstallWithModrinthApp.vue'
type DownloadModalProject = Omit<Labrinth.Projects.v2.Project, 'project_type'> & {
project_type: DisplayProjectType
actualProjectType: Labrinth.Projects.v2.ProjectType
}
type ProjectDownloadSelection = {
currentGameVersion: string | null
currentPlatform: string | null
selectedVersion: Labrinth.Versions.v3.Version | null
selectedPrimaryFile: Labrinth.Versions.v3.VersionFile | null
}
type DownloadableFile = {
href: string
filename: string
}
type NewModalRef = {
show: (event?: MouseEvent) => void
hide: () => void
}
type ProjectDownloadModalShowOptions = {
projectId?: string
incompatibleGameVersions?: string[]
incompatibleLoaders?: string[]
}
type ResolvedProjectDownloadModalShowOptions = {
projectId?: string
incompatibleGameVersions: string[]
incompatibleLoaders: string[]
}
const props = withDefaults(
defineProps<{
projectId?: string
downloadReason?: CdnDownloadReason
useRouteHash?: boolean
updateRouteSelection?: boolean
}>(),
{
downloadReason: 'standalone',
useRouteHash: true,
updateRouteSelection: true,
},
)
const emit = defineEmits<{
download: []
}>()
const route = useRoute()
const flags = useFeatureFlags()
const tags = useGeneratedState()
const client = injectModrinthClient()
const { formatMessage } = useVIntl()
const debug = useDebugLogger('DownloadModal')
const modal = ref<NewModalRef | null>(null)
const downloadTitleRef = ref<HTMLElement | null>(null)
const modalOpen = ref(false)
const showProjectId = ref<string | null>(null)
const showOptions = ref<ResolvedProjectDownloadModalShowOptions>(getDefaultShowOptions())
const downloadProjectResetKey = ref(0)
const projectDownloadSelection = ref<ProjectDownloadSelection>(getDefaultProjectDownloadSelection())
const dependencyDownloadFiles = ref<DownloadableFile[]>([])
const MODAL_CLOSE_STATE_RESET_MS = 350
let closeStateResetTimeout: ReturnType<typeof setTimeout> | null = null
const routeProjectId = computed(() => showProjectId.value ?? props.projectId ?? null)
const {
data: projectRaw,
error: projectV2Error,
refetch: refetchProject,
} = useQuery({
queryKey: computed(() => ['project', 'v2', routeProjectId.value]),
queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value!),
enabled: computed(() => !!routeProjectId.value),
staleTime: STALE_TIME,
})
const resolvedProjectId = computed(() => projectRaw.value?.id)
const project = computed<DownloadModalProject | null>(() => {
if (!projectRaw.value) return null
return {
...projectRaw.value,
actualProjectType: projectRaw.value.project_type,
project_type: getProjectTypeForUrl(projectRaw.value.project_type, projectRaw.value.loaders),
}
})
const downloadTitle = computed(() => {
if (!project.value) return ''
return formatMessage(messages.downloadTitle, { title: project.value.title })
})
const versionsEnabled = ref(false)
const {
data: versionsV3,
error: _versionsV3Error,
isFetching: versionsV3Loading,
} = useQuery({
queryKey: computed(() => ['project', resolvedProjectId.value, 'versions', 'v3']),
queryFn: () =>
client.labrinth.versions_v3.getProjectVersions(resolvedProjectId.value!, {
include_changelog: false,
apiVersion: 3,
}),
staleTime: STALE_TIME_LONG,
enabled: computed(() => !!resolvedProjectId.value && versionsEnabled.value),
})
const versions = computed<Labrinth.Versions.v3.Version[]>(() => {
const isModpack =
project.value?.actualProjectType === 'modpack' || project.value?.project_type === 'modpack'
return (versionsV3.value ?? []).map((version) => {
const files = Array.isArray(version.files) ? version.files : []
const gameVersions = Array.isArray(version.game_versions) ? version.game_versions : []
const loaders = Array.isArray(version.loaders) ? version.loaders : []
const mrpackLoaders = Array.isArray(version.mrpack_loaders) ? version.mrpack_loaders : []
return {
...version,
files,
game_versions: gameVersions,
loaders: isModpack && mrpackLoaders.length ? mrpackLoaders : loaders,
}
})
})
const initialGameVersion = computed(() => {
const version = route.query.version
if (typeof version !== 'string' || !project.value?.game_versions.includes(version)) return null
return version
})
const initialPlatform = computed(() => {
const loader = route.query.loader
if (typeof loader !== 'string' || !project.value?.loaders.includes(loader)) return null
return loader
})
const currentGameVersion = computed(() => projectDownloadSelection.value.currentGameVersion)
const currentPlatform = computed(() => projectDownloadSelection.value.currentPlatform)
const selectedVersion = computed(() => projectDownloadSelection.value.selectedVersion)
const selectedPrimaryFile = computed(() => projectDownloadSelection.value.selectedPrimaryFile)
const additionalFiles = computed(() => {
if (!selectedVersion.value || !selectedPrimaryFile.value) return []
return selectedVersion.value.files.filter((file) => file !== selectedPrimaryFile.value)
})
watch(projectV2Error, (error) => {
if (error) {
debug('project query failed', error)
}
})
const messages = defineMessages({
downloadTitle: {
id: 'project.download.title',
defaultMessage: 'Download {title}',
},
})
function getProjectTypeForUrl(
type: Labrinth.Projects.v2.ProjectType,
loaders: string[],
): DisplayProjectType {
if (type !== 'mod') return type as DisplayProjectType
const isMod = loaders.some((loader) => tags.value.loaderData.modLoaders.includes(loader))
const isPlugin = loaders.some((loader) => tags.value.loaderData.allPluginLoaders.includes(loader))
const isDataPack = loaders.some((loader) =>
tags.value.loaderData.dataPackLoaders.includes(loader),
)
if (isDataPack) return 'datapack'
if (isPlugin) return 'plugin'
if (isMod) return 'mod'
return 'mod'
}
function updateDownloadQuery({
gameVersion,
platform,
}: {
gameVersion: string | null
platform: string | null
}) {
if (!props.updateRouteSelection) return
navigateTo(
{
query: {
...route.query,
...(gameVersion && {
version: gameVersion,
}),
...(platform && {
loader: platform,
}),
},
hash: route.hash,
},
{ replace: true },
)
}
function selectGameVersion(gameVersion: string) {
updateDownloadQuery({
gameVersion,
platform: currentPlatform.value,
})
}
function selectPlatform(platform: string) {
updateDownloadQuery({
gameVersion: currentGameVersion.value,
platform,
})
}
function onShow() {
clearCloseStateResetTimeout()
modalOpen.value = true
debug('on-show fired')
versionsEnabled.value = true
if (props.useRouteHash && !showProjectId.value) {
navigateTo({ query: route.query, hash: '#download' }, { replace: true })
}
}
function onHide() {
const hadShowProjectId = !!showProjectId.value
modalOpen.value = false
clearCloseStateResetTimeout()
closeStateResetTimeout = setTimeout(() => {
showProjectId.value = null
showOptions.value = getDefaultShowOptions()
closeStateResetTimeout = null
}, MODAL_CLOSE_STATE_RESET_MS)
if (props.useRouteHash && !hadShowProjectId) {
navigateTo({ query: route.query, hash: '' }, { replace: true })
}
}
async function show(
event?: MouseEvent,
options: ProjectDownloadModalShowOptions = {},
): Promise<void> {
if (!modal.value || modalOpen.value) return
await waitForCloseStateReset()
if (!modal.value || modalOpen.value) return
showOptions.value = {
...getDefaultShowOptions(),
...options,
}
showProjectId.value = showOptions.value.projectId ?? null
await nextTick()
if (!(await loadProjectForModal(!!showOptions.value.projectId))) return
resetDownloadState()
modalOpen.value = true
modal.value.show(event)
}
function hide() {
if (!modal.value || !modalOpen.value) return
modal.value?.hide()
}
function onDownload() {
emit('download')
}
function getDefaultProjectDownloadSelection(): ProjectDownloadSelection {
return {
currentGameVersion: null,
currentPlatform: null,
selectedVersion: null,
selectedPrimaryFile: null,
}
}
function getDefaultShowOptions(): ResolvedProjectDownloadModalShowOptions {
return {
projectId: undefined,
incompatibleGameVersions: [],
incompatibleLoaders: [],
}
}
function clearCloseStateResetTimeout() {
if (!closeStateResetTimeout) return
clearTimeout(closeStateResetTimeout)
closeStateResetTimeout = null
}
async function waitForCloseStateReset() {
if (!closeStateResetTimeout) return
await new Promise((resolve) => setTimeout(resolve, MODAL_CLOSE_STATE_RESET_MS))
}
async function loadProjectForModal(forceRefetch: boolean) {
if (!routeProjectId.value) return false
if (!forceRefetch && projectRaw.value) return true
const { data } = await refetchProject()
return !!data
}
function resetDownloadState() {
projectDownloadSelection.value = getDefaultProjectDownloadSelection()
dependencyDownloadFiles.value = []
downloadProjectResetKey.value += 1
}
function openFromHash() {
if (
!props.useRouteHash ||
!modal.value ||
modalOpen.value ||
showProjectId.value ||
route.hash !== '#download'
) {
return
}
debug('hash #download watch fired, opening modal')
show()
}
if (
props.useRouteHash &&
(route.hash === '#download' ||
route.query.version !== undefined ||
route.query.loader !== undefined)
) {
debug('eager loadVersions from setup', {
hash: route.hash,
version: route.query.version,
loader: route.query.loader,
loading: versionsV3Loading.value,
})
versionsEnabled.value = true
}
watch(modal, openFromHash)
watch(() => route.hash, openFromHash)
watch(routeProjectId, () => {
projectDownloadSelection.value = getDefaultProjectDownloadSelection()
dependencyDownloadFiles.value = []
downloadProjectResetKey.value += 1
})
onUnmounted(clearCloseStateResetTimeout)
defineExpose({ show, hide })
</script>
@@ -257,7 +257,7 @@ const messages = defineMessages({
},
managePasskeyModalLoading: {
id: 'settings.account.security.passkey.modal.loading',
defaultMessage: 'Loading passkeys...',
defaultMessage: 'Loading passkeys',
},
managePasskeyModalNoPasskeys: {
id: 'settings.account.security.passkey.modal.no-passkeys',
@@ -73,7 +73,6 @@
:options="[userOption, ...ownerOptions]"
searchable
:disabled="hasHitLimit"
select-search-text-on-focus
show-icon-in-selected
/>
<span>{{ formatMessage(messages.ownerDescription) }}</span>
@@ -130,7 +129,7 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { OrganizationIcon, PlusIcon, XIcon } from '@modrinth/assets'
import { PlusIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Chips,
@@ -363,13 +362,7 @@ async function fetchOrganizations() {
}),
),
)
: markRaw(
defineAsyncComponent(() =>
Promise.resolve({
setup: () => () => h(OrganizationIcon, { class: 'size-5' }),
}),
),
),
: undefined,
}))
if (props.organizationId) owner.value = props.organizationId
} catch (err) {
@@ -402,7 +395,6 @@ async function createProject() {
server_side: 'required',
license_id: 'LicenseRef-Unknown',
is_draft: true,
organization_id: owner.value !== 'self' ? owner.value : undefined,
}
formData.append('data', JSON.stringify(projectData))
@@ -2,7 +2,12 @@
<div class="shadow-card rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<NuxtLink :to="`/project/${projectRouteParam}`" target="_blank" tabindex="-1" class="flex">
<NuxtLink
:to="`/project/${queueEntry.project.slug}`"
target="_blank"
tabindex="-1"
class="flex"
>
<Avatar
:src="queueEntry.project.icon_url"
size="4rem"
@@ -12,7 +17,7 @@
<div class="flex flex-col gap-1.5">
<div class="flex items-center gap-2">
<NuxtLink
:to="`/project/${projectRouteParam}`"
:to="`/project/${queueEntry.project.slug}`"
target="_blank"
class="text-lg font-semibold text-contrast hover:underline"
>
@@ -172,7 +177,7 @@ function getDaysQueued(date: Date): number {
const queuedDate = computed(() => {
return dayjs(
props.queueEntry.project.queued ||
props.queueEntry.project.published ||
props.queueEntry.project.created ||
props.queueEntry.project.updated,
)
})
@@ -181,14 +186,10 @@ const daysInQueue = computed(() => {
return getDaysQueued(queuedDate.value.toDate())
})
const projectRouteParam = computed(
() => props.queueEntry.project.slug || props.queueEntry.project.id,
)
const formattedDate = computed(() => {
const date =
props.queueEntry.project.queued ||
props.queueEntry.project.published ||
props.queueEntry.project.created ||
props.queueEntry.project.updated
if (!date) return 'Unknown'
@@ -201,7 +202,7 @@ const formattedDate = computed(() => {
function copyLink() {
const base = window.location.origin
const projectUrl = `${base}/project/${projectRouteParam.value}`
const projectUrl = `${base}/project/${props.queueEntry.project.slug}`
navigator.clipboard.writeText(projectUrl).then(() => {
addNotification({
type: 'success',
@@ -10,8 +10,9 @@ import {
CodeIcon,
CopyIcon,
DownloadIcon,
ExternalIcon,
EllipsisVerticalIcon,
EyeOffIcon,
LinkIcon,
LoaderCircleIcon,
ScaleIcon,
ShieldCheckIcon,
@@ -112,6 +113,51 @@ const isProjectApproved = computed(() => {
)
})
const quickActions = computed<OverflowMenuOption[]>(() => {
const actions: OverflowMenuOption[] = []
const sourceUrl = props.item.project.link_urls?.['source']?.url
if (sourceUrl) {
actions.push({
id: 'view-source',
action: () => {
window.open(sourceUrl, '_blank', 'noopener,noreferrer')
},
})
}
actions.push(
{
id: 'copy-link',
action: () => {
const base = window.location.origin
const reportUrl = `${base}/moderation/technical-review/${props.item.project.id}`
navigator.clipboard.writeText(reportUrl).then(() => {
addNotification({
type: 'success',
title: 'Technical Review link copied',
text: 'The link to this review has been copied to your clipboard.',
})
})
},
},
{
id: 'copy-id',
action: () => {
navigator.clipboard.writeText(props.item.project.id).then(() => {
addNotification({
type: 'success',
title: 'Project ID copied',
text: 'The ID of this project has been copied to your clipboard.',
})
})
},
},
)
return actions
})
const isLoadingStatusAction = ref(false)
const projectStatusActions = computed<OverflowMenuOption[]>(() => [
{
@@ -147,7 +193,6 @@ async function setStatus(status: Labrinth.Projects.v2.ProjectStatus) {
isLoadingStatusAction.value = true
try {
await client.labrinth.projects_v2.edit(props.item.project.id, { status })
emit('refetch')
projectStatus.value = status
} catch (err) {
@@ -974,16 +1019,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
}
}
}
function copyId() {
navigator.clipboard.writeText(props.item.project.id).then(() => {
addNotification({
type: 'success',
title: 'Project ID copied',
text: 'The ID of this project has been copied to your clipboard.',
})
})
}
</script>
<template>
@@ -1069,31 +1104,25 @@ function copyId() {
<div class="flex items-center gap-3">
<span class="text-base text-secondary">{{ formattedDate }}</span>
<div class="flex items-center gap-2">
<ButtonStyled v-if="props.item.project.link_urls?.['source']?.url" circular>
<a
v-tooltip="'Open sources in new tab'"
:href="props.item.project.link_urls?.['source']?.url"
target="_blank"
>
<CodeIcon />
</a>
</ButtonStyled>
<ButtonStyled circular>
<button v-tooltip="'Copy ID'" @click="copyId">
<ButtonStyled circular>
<OverflowMenu :options="quickActions" class="!shadow-none">
<template #default>
<EllipsisVerticalIcon class="size-4" />
</template>
<template #copy-id>
<ClipboardCopyIcon />
</button>
</ButtonStyled>
<ButtonStyled circular>
<a
v-tooltip="'Open in new tab'"
:href="`/moderation/technical-review/${props.item.project.id}`"
target="_blank"
>
<ExternalIcon />
</a>
</ButtonStyled>
</div>
<span class="hidden sm:inline">Copy ID</span>
</template>
<template #copy-link>
<LinkIcon />
<span class="hidden sm:inline">Copy link</span>
</template>
<template #view-source>
<CodeIcon />
<span class="hidden sm:inline">View source</span>
</template>
</OverflowMenu>
</ButtonStyled>
</div>
</div>
@@ -1177,7 +1206,7 @@ function copyId() {
</OverflowMenu>
</ButtonStyled>
<ButtonStyled v-if="featureFlags.developerMode" type="outlined">
<button @click="emit('showMaliciousSummary', unsafeFiles)">Debug</button>
<button @click="emit('showMaliciousSummary', unsafeFiles)">Debug Summary</button>
</ButtonStyled>
</template>
</ThreadView>
@@ -1,326 +0,0 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { FolderSearchIcon, StarIcon } from '@modrinth/assets'
import {
ButtonStyled,
defineMessages,
injectModrinthClient,
NewModal,
Table,
type TableColumn,
useVIntl,
} from '@modrinth/ui'
import { computed, ref, useTemplateRef } from 'vue'
const messages = defineMessages({
title: {
id: 'modpack-scan-modal.title',
defaultMessage: 'Modpack Scan ({scanned}/{total} Files)',
},
scanAllFiles: {
id: 'modpack-scan-modal.scan-all-files',
defaultMessage: 'Scan All Files',
},
packFileName: {
id: 'modpack-scan-modal.pack-file-name',
defaultMessage: 'Pack File Name',
},
newFiles: {
id: 'modpack-scan-modal.new-files',
defaultMessage: 'New Files',
},
newGroups: {
id: 'modpack-scan-modal.new-groups',
defaultMessage: 'New Groups',
},
loadingVersions: {
id: 'modpack-scan-modal.loading-versions',
defaultMessage: 'Loading versions...',
},
noFiles: {
id: 'modpack-scan-modal.no-files',
defaultMessage: 'No files found.',
},
notScanned: {
id: 'modpack-scan-modal.not-scanned',
defaultMessage: 'Not scanned',
},
scanning: {
id: 'modpack-scan-modal.scanning',
defaultMessage: 'Scanning...',
},
failed: {
id: 'modpack-scan-modal.failed',
defaultMessage: 'Failed',
},
overrideFiles: {
id: 'modpack-scan-modal.override-files',
defaultMessage: 'Override Files ({count})',
},
loadVersionsError: {
id: 'modpack-scan-modal.load-versions-error',
defaultMessage: 'Failed to load versions: {error}',
},
scanError: {
id: 'modpack-scan-modal.scan-error',
defaultMessage: 'Some files failed to scan: {error}',
},
})
type ScanTableColumn = 'filename' | 'newFiles' | 'newGroups'
type ScanRow = {
id: string
filename: string
primary: boolean
scan?: Labrinth.Attribution.Internal.FileScanResponse
isScanning: boolean
error?: string
newFiles?: number
newGroups?: number
}
const props = defineProps<{
project_id: string
}>()
const client = injectModrinthClient()
const modalRef = useTemplateRef<InstanceType<typeof NewModal>>('modalRef')
const { formatMessage } = useVIntl()
const rows = ref<ScanRow[]>([])
const isLoadingVersions = ref(false)
const isScanning = ref(false)
const versionLoadError = ref<string | null>(null)
const scanError = ref<string | null>(null)
const requestId = ref(0)
const scanRequestId = ref(0)
const columns = computed<TableColumn<ScanTableColumn>[]>(() => [
{ key: 'filename', label: formatMessage(messages.packFileName), width: '60%' },
{ key: 'newFiles', label: formatMessage(messages.newFiles), align: 'center', width: '20%' },
{ key: 'newGroups', label: formatMessage(messages.newGroups), align: 'center', width: '20%' },
])
const scannedCount = computed(() => rows.value.filter((row) => row.scan || row.error).length)
const isBusy = computed(() => isLoadingVersions.value || isScanning.value)
function getErrorMessage(error: unknown) {
if (error instanceof Error) {
return error.message
}
if (typeof error === 'object' && error !== null && 'data' in error) {
const data = (error as { data?: { description?: string } }).data
if (data?.description) {
return data.description
}
}
return String(error)
}
async function fetchAllVersions() {
const currentRequestId = ++requestId.value
isLoadingVersions.value = true
versionLoadError.value = null
scanError.value = null
rows.value = []
try {
const versions = await client.labrinth.versions_v2.getProjectVersions(props.project_id)
if (currentRequestId !== requestId.value) {
return
}
rows.value = versions
.flatMap((version) => version.files)
.filter((file): file is Labrinth.Versions.v2.VersionFile & { id: string } => Boolean(file.id))
.map((file) => ({
id: file.id,
filename: file.filename,
primary: file.primary,
isScanning: false,
newFiles: undefined,
newGroups: undefined,
}))
} catch (error) {
if (currentRequestId === requestId.value) {
versionLoadError.value = formatMessage(messages.loadVersionsError, {
error: getErrorMessage(error),
})
}
} finally {
if (currentRequestId === requestId.value) {
isLoadingVersions.value = false
}
}
}
async function fetchAllScans() {
if (isBusy.value) {
return
}
const currentScanRequestId = ++scanRequestId.value
isScanning.value = true
scanError.value = null
rows.value = rows.value.map((row) => ({
...row,
scan: undefined,
isScanning: false,
error: undefined,
newFiles: undefined,
newGroups: undefined,
}))
try {
for (const row of rows.value) {
if (currentScanRequestId !== scanRequestId.value) {
return
}
row.isScanning = true
try {
const scan = await client.labrinth.attribution_internal.scanFile(row.id)
if (currentScanRequestId !== scanRequestId.value) {
return
}
row.scan = scan
row.newFiles = scan.new_attribution_files
row.newGroups = scan.new_attribution_groups
} catch (error) {
if (currentScanRequestId !== scanRequestId.value) {
return
}
row.error = getErrorMessage(error)
scanError.value = formatMessage(messages.scanError, { error: row.error })
} finally {
row.isScanning = false
}
}
} finally {
if (currentScanRequestId === scanRequestId.value) {
isScanning.value = false
}
}
}
function show() {
scanRequestId.value++
isScanning.value = false
rows.value = []
void fetchAllVersions()
modalRef.value?.show()
}
function hide() {
modalRef.value?.hide()
}
defineExpose({ show, hide })
</script>
<template>
<NewModal
ref="modalRef"
width="60vw"
:close-on-click-outside="false"
:close-on-esc="false"
:disable-close="isBusy"
>
<template #title>
<div class="flex w-full items-center justify-between gap-2">
<span class="text-2xl font-semibold text-contrast">
{{
formatMessage(messages.title, {
scanned: scannedCount,
total: rows.length,
})
}}
</span>
<div>
<ButtonStyled circular>
<button
v-tooltip="formatMessage(messages.scanAllFiles)"
:disabled="isBusy || rows.length === 0"
@click="fetchAllScans"
>
<FolderSearchIcon aria-hidden="true" />
</button>
</ButtonStyled>
</div>
</div>
</template>
<div class="w-full">
<div
v-if="versionLoadError || scanError"
class="mb-3 rounded-xl bg-highlight-red p-3 text-red"
>
{{ versionLoadError || scanError }}
</div>
<Table
:columns="columns"
:data="rows"
row-key="id"
:row-below-visible="
(row) => Boolean(row.scan?.scanned_file_names && row.scan.scanned_file_names.length > 0)
"
table-min-width="42rem"
>
<template #cell-filename="{ row }">
<div class="flex min-w-0 items-center gap-1 text-contrast">
<StarIcon v-if="row.primary" class="size-4 shrink-0" aria-hidden="true" />
<span class="min-w-0 truncate">{{ row.filename }}</span>
</div>
</template>
<template #cell-newFiles="{ row }">
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
<span v-else-if="row.error" v-tooltip="row.error" class="text-red">
{{ formatMessage(messages.failed) }}
</span>
<span v-else-if="row.scan">{{ row.scan.new_attribution_files }}</span>
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
</template>
<template #cell-newGroups="{ row }">
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
<span v-else-if="row.error" v-tooltip="row.error" class="text-red">
{{ formatMessage(messages.failed) }}
</span>
<span v-else-if="row.scan">{{ row.scan.new_attribution_groups }}</span>
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
</template>
<template #row-below="{ row }">
<div class="border-0 border-t border-solid border-surface-4 px-4 py-3">
<details>
<summary>
{{
formatMessage(messages.overrideFiles, {
count: row.scan?.scanned_file_names.length ?? 0,
})
}}
</summary>
<div class="flex flex-wrap gap-1 pt-2">
<span
v-for="name of row.scan?.scanned_file_names ?? []"
:key="name"
v-tooltip="name"
class="flex items-center gap-1 text-wrap rounded-full bg-button-bg px-2 py-0.5 text-xs font-medium text-contrast"
>
{{ name }}
</span>
</div>
</details>
</div>
</template>
<template #empty-state>
<div class="flex h-64 items-center justify-center text-secondary">
{{ formatMessage(isLoadingVersions ? messages.loadingVersions : messages.noFiles) }}
</div>
</template>
</Table>
</div>
</NewModal>
</template>
@@ -1949,10 +1949,7 @@ function generateModpackMessage(allFiles: {
const hasNextProject = ref(false)
async function refreshModerationCaches(threadId?: string) {
const refreshes: Promise<unknown>[] = [
invalidate(),
queryClient.invalidateQueries({ queryKey: ['moderation-projects'] }),
]
const refreshes: Promise<unknown>[] = [invalidate(), refreshNuxtData('moderation-projects')]
if (threadId) {
refreshes.push(queryClient.invalidateQueries({ queryKey: ['thread', threadId] }))
@@ -21,7 +21,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
developerMode: false,
demoMode: false,
showVersionFilesInTable: false,
showVersionEnvironmentColumn: false,
showAdsWithPlus: false,
alwaysShowChecklistAsPopup: true,
testTaxForm: false,
@@ -4,7 +4,6 @@ import type {
BrowseInstallPlan,
BrowseSearchState,
CreationFlowContextValue,
EnvironmentSearchOverride,
FilterValue,
PendingServerContentInstall,
PendingServerContentInstallType,
@@ -22,8 +21,6 @@ import {
readStoredServerInstallQueue,
removePendingServerContentInstall,
requestInstall,
stripServerRuntimeInstallFilters,
stripServerRuntimeInstallOverrides,
useVIntl,
writePendingServerContentInstallBaseline,
writeStoredServerInstallQueue,
@@ -163,7 +160,6 @@ export function useServerInstallContent({
})
const serverHideInstalled = ref(false)
const serverContentServerOnly = ref(false)
const hideSelectedServerInstalls = ref(false)
const installingProjectIds = ref<Set<string>>(new Set())
const optimisticallyInstalledProjectIds = ref<Set<string>>(new Set())
@@ -352,6 +348,10 @@ export function useServerInstallContent({
filters.push({ type: 'plugin_loader', option: platform })
}
if (projectType.value?.id === 'mod') {
filters.push({ type: 'environment', option: 'server' })
}
if (serverHideInstalled.value && hiddenInstalledProjectIds.value.size > 0) {
for (const x of hiddenInstalledProjectIds.value) {
filters.push({
@@ -383,28 +383,6 @@ export function useServerInstallContent({
return filters
})
const showServerOnlyToggle = computed(() => !!serverData.value && projectType.value?.id === 'mod')
const serverEnvironmentOverride = computed<EnvironmentSearchOverride | undefined>(() => {
if (!showServerOnlyToggle.value) return undefined
if (serverContentServerOnly.value) {
return {
mode: 'include',
values: [
'server_only',
'dedicated_server_only',
'server_only_client_optional',
'client_or_server_prefers_both',
'client_or_server',
],
}
}
return {
mode: 'exclude',
values: ['client_only', 'singleplayer_only'],
}
})
function getCurrentServerInstallType(): BrowseInstallContentType {
const type = projectType.value?.id
if (type === 'modpack' || type === 'mod' || type === 'plugin' || type === 'datapack') {
@@ -606,15 +584,11 @@ export function useServerInstallContent({
project,
contentType,
mode: isModpack ? 'immediate' : 'queue',
selectedFilters: isModpack
? []
: stripServerRuntimeInstallFilters(browseSearchState.currentFilters.value),
selectedFilters: isModpack ? [] : browseSearchState.currentFilters.value,
providedFilters: isModpack ? [] : serverFilters.value,
overriddenProvidedFilterTypes: isModpack
? []
: stripServerRuntimeInstallOverrides(
browseSearchState.overriddenProvidedFilterTypes.value,
),
: browseSearchState.overriddenProvidedFilterTypes.value,
targetPreferences: getServerInstallTargetPreferences(contentType),
getProjectVersions: getInstallProjectVersions,
queue: serverInstallQueue,
@@ -763,10 +737,6 @@ export function useServerInstallContent({
serverHideInstalled.value = route.query.shi === 'true'
}
if (route.query.so && projectType.value?.id === 'mod') {
serverContentServerOnly.value = route.query.so === 'true'
}
watch(serverHideInstalled, (hideInstalled) => {
if (hideInstalled) {
syncHiddenInstalledProjectIds()
@@ -793,9 +763,6 @@ export function useServerInstallContent({
serverContentData,
serverFilters,
serverHideInstalled,
serverContentServerOnly,
showServerOnlyToggle,
serverEnvironmentOverride,
hideSelectedServerInstalls,
installingProjectIds,
optimisticallyInstalledProjectIds,
+6 -3
View File
@@ -1,4 +1,3 @@
import type { Labrinth } from '@modrinth/api-client'
import type { ExtendedReport, OwnershipTarget } from '@modrinth/moderation'
import type {
Organization,
@@ -198,10 +197,14 @@ export interface ModerationOwnershipOrganization {
export type ModerationOwnership = ModerationOwnershipUser | ModerationOwnershipOrganization
export type ProjectWithOwnership = Labrinth.Moderation.Internal.QueueProject
export interface ProjectWithOwnership {
ownership: ModerationOwnership
external_dependencies_count: number
[key: string]: any
}
export interface ModerationProject {
project: Omit<ProjectWithOwnership, 'ownership' | 'external_dependencies_count'>
project: any
ownership: ModerationOwnership | null
external_dependencies_count: number
}
-48
View File
@@ -5,10 +5,6 @@
<div class="pointer-events-none absolute inset-0 z-[-1]">
<div id="absolute-background-teleport" class="relative"></div>
</div>
<div
class="pride-backdrop pointer-events-none absolute inset-0 z-[-1]"
:class="{ shown: showPrideBackdrop }"
></div>
<div class="pointer-events-none absolute inset-0 z-50">
<div
class="over-the-top-random-animation"
@@ -805,7 +801,6 @@ import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
import ModrinthFooter from '~/components/ui/ModrinthFooter.vue'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { errors as generatedStateErrors } from '~/generated/state.json'
import { provideCurrentProjectId } from '~/providers/current-project.ts'
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
import { hasActiveMidas } from '~/utils/user-membership.ts'
@@ -872,32 +867,6 @@ const showTinMismatchBanner = computed(() => {
return !!auth.value.user && status === 'tin-mismatch'
})
const PRIDE_COLLECTION_ID = 'M4c3ITvd'
const PRIDE_ARTICLE_SLUGS = ['pride-campaign-2025', 'pride-campaign-2026', 'proud-of-you-2026']
const PRIDE_CACHE_TIME = 1000 * 60 * 60 * 24
const { data: prideCollection } = useQuery({
queryKey: computed(() => ['collection', PRIDE_COLLECTION_ID]),
queryFn: () => client.labrinth.collections.get(PRIDE_COLLECTION_ID),
staleTime: PRIDE_CACHE_TIME,
gcTime: PRIDE_CACHE_TIME,
})
const prideProjectIds = computed(() => new Set(prideCollection.value?.projects ?? []))
const currentProjectId = ref()
provideCurrentProjectId(currentProjectId)
const showPrideBackdrop = computed(() => {
if (PRIDE_ARTICLE_SLUGS.includes(route.params.slug)) {
return true
}
if (route.params.collection === PRIDE_COLLECTION_ID) {
return true
}
return !!currentProjectId.value && prideProjectIds.value.has(currentProjectId.value)
})
const basePopoutId = useId()
async function fetchIntercomToken() {
@@ -1726,21 +1695,4 @@ const { cycle: changeTheme } = useTheme()
transform: translateY(0);
}
}
.pride-backdrop {
background-image: linear-gradient(to right, #c20732, #f57203, #ffd632, #21ca8b, #2f9ff2, #e420fc);
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0) 80%);
height: 30rem;
opacity: 0;
transition: opacity 1s ease;
}
.pride-backdrop.shown {
opacity: 0.08;
}
.light-mode .pride-backdrop.shown,
.light .pride-backdrop.shown {
opacity: 0.15;
}
</style>
@@ -356,9 +356,18 @@
"auth.reset-password.title.long": {
"message": "تغيير كلمة المرور"
},
"auth.sign-in.2fa.description": {
"message": "يرجى إدخال رمز التحقق الثنائي للمتابعة."
},
"auth.sign-in.2fa.label": {
"message": "أدخل رمز التحقق الثنائي"
},
"auth.sign-in.2fa.placeholder": {
"message": "أدخل الرمز..."
},
"auth.sign-in.sign-in-with": {
"message": "تسجيل الدخول باستخدام"
},
"auth.sign-in.title": {
"message": "تسجيل الدخول"
},
@@ -371,6 +380,9 @@
"auth.sign-up.title": {
"message": "إنشاء حساب"
},
"auth.sign-up.title.sign-up-with": {
"message": "سجّل باستخدام"
},
"auth.verify-email.action.account-settings": {
"message": "إعدادات الحساب"
},
@@ -914,6 +926,18 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "يرجى مراجعة بيانات تعريف البيئة"
},
"dashboard.projects.sort.ascending": {
"message": "صعوداً"
},
"dashboard.projects.sort.descending": {
"message": "نزولا"
},
"dashboard.projects.sort.option.name": {
"message": "الإسم"
},
"dashboard.projects.sort.option.status": {
"message": "الحالة"
},
"dashboard.revenue.available-now": {
"message": "متاح الآن"
},
File diff suppressed because it is too large Load Diff
@@ -254,9 +254,18 @@
"auth.reset-password.title.long": {
"message": "Nulstil din adgangskode"
},
"auth.sign-in.2fa.description": {
"message": "Indtast en to-faktor-kode for at fortsætte."
},
"auth.sign-in.2fa.label": {
"message": "Indtast to-faktor-kode"
},
"auth.sign-in.2fa.placeholder": {
"message": "Indtast kode..."
},
"auth.sign-in.sign-in-with": {
"message": "Log ind med"
},
"auth.sign-in.title": {
"message": "Log ind"
},
@@ -269,6 +278,9 @@
"auth.sign-up.title": {
"message": "Tilmeld"
},
"auth.sign-up.title.sign-up-with": {
"message": "Tilmeld dig med"
},
"auth.verify-email.action.account-settings": {
"message": "Kontoindstillinger"
},
@@ -1760,21 +1772,45 @@
"project.description.title": {
"message": "Beskrivelse"
},
"project.download.game-version": {
"message": "Spille version: {version}"
},
"project.download.game-version-error": {
"message": "Fejl: ingen spil versioner fundet"
},
"project.download.game-version-tooltip": {
"message": "{title} er kun tilgængelig til {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} understøtter ikke {gameVersion} til {platform}"
},
"project.download.install-with-app": {
"message": "Installer med Modrinth App"
},
"project.download.no-app": {
"message": "Har ikke Modrinth App?"
},
"project.download.no-versions-available": {
"message": "Ingen versioner tilgængelig til {gameVersion} og {platform}."
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Fejl: ingen platforme fundet"
},
"project.download.platform-tooltip": {
"message": "{title} er kun tilgængelig til {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} understøtter ikke {platform} til {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Søg spil versioner..."
},
"project.download.search-game-versions-label": {
"message": "Søg spil versioner..."
},
"project.download.select-game-version": {
"message": "Vælg spil version"
},
+59 -326
View File
@@ -23,15 +23,6 @@
"analytics.breakdown.country": {
"message": "Land"
},
"analytics.breakdown.dependent-on": {
"message": "Abhängig von"
},
"analytics.breakdown.dependent-project-download": {
"message": "Abhängiges Projekt"
},
"analytics.breakdown.dependent-project-type": {
"message": "Abhängiger Projekttyp"
},
"analytics.breakdown.download-reason": {
"message": "Download-Grund"
},
@@ -47,9 +38,6 @@
"analytics.breakdown.loader": {
"message": "Loader"
},
"analytics.breakdown.members": {
"message": "Mitglied"
},
"analytics.breakdown.monetization": {
"message": "Monetarisierung"
},
@@ -146,24 +134,6 @@
"analytics.chart.render-limit.header": {
"message": "Alle {count} Linien im Diagramm anzeigen?"
},
"analytics.chart.table-selection.all": {
"message": "Zeige alle {itemType, select, project {{count, plural, one {Projekt} other {Projekte}}} country {{count, plural, one {Land} other {Länder}}} monetization {{count, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{count, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{count, plural, one {Downloadgrund} other {Downloadgründe}}} member {{count, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{count, plural, one {Projektversion} other {Projektversionen}}} loader {{count, plural, one {Loader} other {Loader}}} gameVersion {{count, plural, one {Spielversion} other {Spielversionen}}} other {{count, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.table-selection.count": {
"message": "Zeige {count} {itemType, select, project {{count, plural, one {Projekt} other {Projekte}}} country {{count, plural, one {Land} other {Länder}}} monetization {{count, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{count, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{count, plural, one {Downloadgrund} other {Downloadgründe}}} member {{count, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{count, plural, one {Projektversion} other {Projektversionen}}} loader {{count, plural, one {Loader} other {Loader}}} gameVersion {{count, plural, one {Spielversion} other {Spielversionen}}} other {{count, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.table-selection.limited": {
"message": "Zeige {limit} {itemType, select, project {{limit, plural, one {Projekt} other {Projekte}}} country {{limit, plural, one {Land} other {Länder}}} monetization {{limit, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{limit, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{limit, plural, one {Downloadgrund} other {Downloadgründe}}} member {{limit, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{limit, plural, one {Projektversion} other {Projektversionen}}} loader {{limit, plural, one {Loader} other {Loader}}} gameVersion {{limit, plural, one {Spielversion} other {Spielversionen}}} other {{limit, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.table-selection.top": {
"message": "Zeige die top {count} {itemType, select, project {{count, plural, one {Projekt} other {Projekte}}} country {{count, plural, one {Land} other {Länder}}} monetization {{count, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{count, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{count, plural, one {Downloadgrund} other {Downloadgründe}}} member {{count, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{count, plural, one {Projektversion} other {Projektversionen}}} loader {{count, plural, one {Loader} other {Loader}}} gameVersion {{count, plural, one {Spielversion} other {Spielversionen}}} other {{count, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.tooltip.dependent-on-project": {
"message": "Abhängig von {project}"
},
"analytics.chart.tooltip.dependent-project-version": {
"message": "{dependentProject} abhängig von {dependencyProject}, {version}"
},
"analytics.chart.tooltip.duration.days": {
"message": "{count, plural, one {# Tag} other {# Tage}}"
},
@@ -248,15 +218,9 @@
"analytics.filter.search.countries": {
"message": "Länder suchen..."
},
"analytics.filter.search.dependent-projects": {
"message": "Projekte durchsuchen..."
},
"analytics.filter.search.download-sources": {
"message": "Download-Quellen suchen..."
},
"analytics.filter.search.members": {
"message": "Mitglieder durchsuchen..."
},
"analytics.filter.search.project-versions": {
"message": "Projektversionen suchen..."
},
@@ -503,12 +467,6 @@
"analytics.value.monetized": {
"message": "Monetarisiert"
},
"analytics.value.no-dependent": {
"message": "Keine"
},
"analytics.value.no-dependent-tooltip": {
"message": "Aus anderen Gründen als einer Abhängigkeit heruntergeladen"
},
"analytics.value.none": {
"message": "Keine"
},
@@ -731,72 +689,12 @@
"auth.authorize.authorize-app-name": {
"message": "{appName} autorisieren"
},
"auth.authorize.error.missing-parameters": {
"message": "Erforderliche Abfrageparameter für OAuth fehlen."
},
"auth.authorize.error.no-redirect-url": {
"message": "Keine Weiterleitungsziel in Antwort gefunden"
},
"auth.authorize.errro-title": {
"message": "Ein Fehler ist aufgetreten"
},
"auth.authorize.redirect-url": {
"message": "Du wirst zu <redirect-url>{url}</redirect-url> weitergeleitet"
},
"auth.continue-with-provider": {
"message": "Weiter mit {provider}"
},
"auth.create-account.age-requirement.warning-title": {
"message": "Mindestalter"
},
"auth.create-account.complete-sign-up": {
"message": "Registrierung abschließen"
},
"auth.create-account.date-of-birth.invalid.text": {
"message": "Bitte gib ein gültiges Geburtsdatum ein. Das Jahr darf nicht 0000 sein."
},
"auth.create-account.date-of-birth.invalid.title": {
"message": "Ungültiges Geburtsdatum"
},
"auth.create-account.date-of-birth.label": {
"message": "Geburtsdatum"
},
"auth.create-account.date-of-birth.placeholder": {
"message": "Wähle dein Geburtsdatum"
},
"auth.create-account.date-of-birth.required.text": {
"message": "Bitte gib dein Geburtsdatum ein, bevor du fortfährst."
},
"auth.create-account.date-of-birth.required.title": {
"message": "Geburtsdatum erforderlich"
},
"auth.create-account.date-of-birth.under13-helper": {
"message": "Du erfüllst das Mindestalter für die Erstellung eines Kontos bei Modrinth nicht."
},
"auth.create-account.info-panel.source-code-link": {
"message": "Relevanter Quellcode"
},
"auth.create-account.info-panel.text": {
"message": "Wir speichern dein Geburtsdatum nicht. Wir verwenden es ausschließlich bei der Registrierung, um die Einhaltung der COPPA-Vorschriften sicherzustellen."
},
"auth.create-account.page-title": {
"message": "Konto erstellen"
},
"auth.create-account.security-check.label": {
"message": "Sicherheitscheck"
},
"auth.create-account.subscribe.label": {
"message": "Haltet mich per E-Mail über coole Dinge auf dem Laufenden, an denen Modrinth gerade arbeitet"
},
"auth.create-account.title": {
"message": "Ein Konto erstellen"
},
"auth.create-account.username.label": {
"message": "Benutzername"
},
"auth.create-account.username.placeholder": {
"message": "Benutzername eingeben"
},
"auth.reset-password.method-choice.action": {
"message": "Wiederherstellungs-E-Mail senden"
},
@@ -831,53 +729,23 @@
"message": "Dein Passwort zurücksetzen"
},
"auth.sign-in.2fa.description": {
"message": "Gib den 6-stelligen Code aus deiner Authentifikator-App oder einen deiner Backup-Codes ein."
"message": "Bitte gib einen Zwei-Faktor-Code ein, um fortzufahren."
},
"auth.sign-in.2fa.label": {
"message": "Zwei-Faktor-Authentifizierung"
"message": "Zwei-Faktor-Code eingeben"
},
"auth.sign-in.2fa.placeholder": {
"message": "Code eingeben..."
},
"auth.sign-in.continue-with-email": {
"message": "Weiter mit E-Mail"
},
"auth.sign-in.continue-with-passkey": {
"message": "Weiter mit Passkey"
},
"auth.sign-in.create-account": {
"message": "Registrieren"
},
"auth.sign-in.forgot-password": {
"message": "Passwort vergessen"
},
"auth.sign-in.last-sign-in": {
"message": "Zuletzt benutzt"
},
"auth.sign-in.no-account": {
"message": "Kein Account?"
},
"auth.sign-in.sign-in-with": {
"message": "Einloggen"
"message": "Anmelden mit"
},
"auth.sign-in.title": {
"message": "Anmelden"
},
"auth.sign-up.age-requirement.warning-title": {
"message": "Mindestalter"
},
"auth.sign-up.continue-with-email": {
"message": "Mit E-Mail fortfahren"
},
"auth.sign-up.legal-dislaimer": {
"message": "Durch das erstellen eines Kontos stimmst du den <terms-link>Nutzungsbedingungen</terms-link> und <privacy-policy-link>Datenschutzrichtlinien</privacy-policy-link> von Modrinth zu."
},
"auth.sign-up.show-fewer-options": {
"message": "Weniger Optionen anzeigen"
},
"auth.sign-up.show-other-options": {
"message": "Andere Optionen anzeigen"
},
"auth.sign-up.sign-in-option.title": {
"message": "Du hast bereits ein Konto?"
},
@@ -885,14 +753,11 @@
"message": "Registrieren"
},
"auth.sign-up.title.sign-up-with": {
"message": "Account erstellen"
"message": "Registrieren mit"
},
"auth.verify-email.action.account-settings": {
"message": "Kontoeinstellungen"
},
"auth.verify-email.action.discover-mods": {
"message": "Mods entdecken"
},
"auth.verify-email.already-verified.description": {
"message": "Deine E-Mail ist bereits verifiziert!"
},
@@ -911,15 +776,6 @@
"auth.verify-email.failed-verification.title": {
"message": "E-Mail-Veifizierung fehlgeschlagen"
},
"auth.verify-email.notification.email-sent.description": {
"message": "Eine E-Mail an {email} wurde zur Verifikation deines Accounts geschickt."
},
"auth.verify-email.notification.email-sent.title": {
"message": "E-Mail gesendet"
},
"auth.verify-email.notification.error-occurred.title": {
"message": "Ein Fehler ist aufgetreten"
},
"auth.verify-email.post-verification.description": {
"message": "Deine E-Mail-Adresse wurde erfolgreich verifiziert!"
},
@@ -1634,6 +1490,9 @@
"dashboard.overview.notifications.loading": {
"message": "Benachrichtigungen werden geladen..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Du kannst mehrere Projekte gleichzeitig bearbeiten, indem du sie unten auswählst."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Serverprojekte understützen Massenbearbeitung nicht"
},
@@ -1709,6 +1568,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Bitte überprüfe die Umgebungsmetadaten"
},
"dashboard.projects.sort.ascending": {
"message": "Aufsteigend"
},
"dashboard.projects.sort.descending": {
"message": "Absteigend"
},
"dashboard.projects.sort.option.name": {
"message": "Name"
},
"dashboard.projects.sort.option.status": {
"message": "Status"
},
"dashboard.projects.sort.option.type": {
"message": "Art"
},
"dashboard.projects.table.icon": {
"message": "Icon"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -3221,21 +3098,45 @@
"project.details.licensed": {
"message": "Lizenziert unter"
},
"project.download.game-version": {
"message": "Spielversion: {version}"
},
"project.download.game-version-error": {
"message": "Fehler: Keine Spielversion gefunden"
},
"project.download.game-version-tooltip": {
"message": "{title} ist nur für {version} verfügbar"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} unterstützt {gameVersion} für {platform} nicht"
},
"project.download.install-with-app": {
"message": "Mit Modrinth App installieren"
},
"project.download.no-app": {
"message": "Du hast die Modrinth App nicht?"
},
"project.download.no-versions-available": {
"message": "Keinen versionen verfügbar für {gameVersion} und {platform}."
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Fehler: Keine Plattform gefunden"
},
"project.download.platform-tooltip": {
"message": "{title} ist nur für {platform} verfügbar"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} unterstützt {platform} für {gameVersion} nicht"
},
"project.download.search-game-versions": {
"message": "Spielversionen durchsuchen..."
},
"project.download.search-game-versions-label": {
"message": "Spielversionen durchuschen..."
},
"project.download.select-game-version": {
"message": "Spielversion wählen"
},
@@ -3426,76 +3327,43 @@
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung folgender Dateien hast. Danach werden die zurückgehaltenen Versionen automatisch veröffentlicht."
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung fgolgender Dateien hast; alle zurückgehaltenen Versionen werden automatisch veröffentlicht."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung aller folgenden Dateien hast, bevor du dein Projekt zur Überprüfung einreichst."
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung fgolgender Dateien hast, bevor du dein Projekt zur überprüfung einreichen kannst."
},
"project.settings.permissions.attention-needed.title": {
"message": "Unbekannter externer Inhalt"
},
"project.settings.permissions.bad-proof.description": {
"message": "Das Moderationsteam von Modrinth hat die von dir für einige externe Inhalte vorgelegten Berechtigungsnachweise abgelehnt. Bitte überprüfe die unten aufgeführten abgelehnten Einträge und lege zulässige Nachweise vor oder entfernen die Inhalte."
},
"project.settings.permissions.bad-proof.title": {
"message": "Manche Beweise wurden abgelehnt"
},
"project.settings.permissions.collapse-all": {
"message": "Alle minimieren"
"message": "Unbekannter eingebetteter Inhalt"
},
"project.settings.permissions.completed.description": {
"message": "Alle externen Inhalte sind mit Erlaubnisangaben versehen, und die Quellenangaben wurden angegeben."
"message": "Alle externen Inhalte haben Quellenangaben."
},
"project.settings.permissions.completed.title": {
"message": "Genehmigungen abgeschlossen!"
"message": "Zuordnungen abgschlossen!"
},
"project.settings.permissions.empty-state.description": {
"message": "Keine der Versionen deines Projekts enthält externe Inhalte, weshalb du dir keine Gedanken über die Einholung von Genehmigungen machen musst."
"message": "Keine deiner Versionen enrhalten externe Inhalte, also musst du dir keine Sorgen über die Einholung von Berechtigungen machen."
},
"project.settings.permissions.empty-state.heading": {
"message": "Du bist startklar!"
},
"project.settings.permissions.expand-all": {
"message": "Alle anzeigen"
},
"project.settings.permissions.fail.description": {
"message": "Möglicherweise verfügst du nicht über die erforderliche Genehmigung zur Weitergabe einiger externer Inhalte in deinem Projekt. Um dein Projekt auf Modrinth zu veröffentlichen, entferne bitte diese Inhalte oder lege einen Nachweis vor, dass du über die erforderliche Genehmigung zur Nutzung verfügst."
"message": "Du hast keine Berechtigung, einige der von dir hinzugefügten externen Inhalte weiterzuverbreiten. Um auf Modrinth veröffentlichen zu können, entferne bitte die urheberrechtswidrigen Inhalte."
},
"project.settings.permissions.fail.title": {
"message": "Einige Inhalte können nicht enthalten sein"
},
"project.settings.permissions.info-banner.description": {
"message": "Wenn du Inhalte einbindest, welche nicht auf Modrinth gehostet werden, musst du uns deren Quelle mitteilen und bestätigen, dass du die Berechtigung zur Verbreitung der Dateien hast. Schau dir <link>unsere Ankündigung zu diesem neuen System</link> an, um mehr zu erfahren!"
"message": "Wenn du Inhalte einbindest, welche nicht auf Modrinth gehostet werden, musst du uns deren Quelle mitteilen und bestätigen, dass du die Berechtigung zur Verbreitung der Dateien hast. In <link>unserer Anleitung (Englisch)</link> lernst du, wie du dies am besten machst!"
},
"project.settings.permissions.info-banner.title": {
"message": "Erfahre mehr über die Rechte zur Verbreitung"
"message": "Lerne wie Zuschreibungen funktionieren"
},
"project.settings.permissions.learn-more": {
"message": "Mehr erfahren"
},
"project.settings.permissions.no-results": {
"message": "Keine externen Dateien übereinstimmen deiner Suche."
},
"project.settings.permissions.not-allowed.description": {
"message": "Einige der enthaltenen externen Inhalte dürfen auf Modrinth nicht verbreitet werden, da sie gegen unsere Inhaltsrichtlinien verstoßen und daher entfernt werden müssen."
},
"project.settings.permissions.pending-approval-count": {
"message": "{count, plural, =0 {Keine Quellenangaben müssen genehmigt werden} one {# Quellenangabe muss genehmigt werden} other {# Quellenangaben müssen genehmigt werden}}"
},
"project.settings.permissions.search-placeholder": {
"message": "Durchsuche {count} {count, plural, one {Projekt} other {Projekten}}..."
},
"project.settings.permissions.sort.most-files": {
"message": "Meiste Dateien"
},
"project.settings.permissions.sort.recently-edited": {
"message": "Letztens bearbeitet"
},
"project.settings.permissions.sort.rejected": {
"message": "Abgelehnt"
},
"project.settings.permissions.sort.status": {
"message": "Status"
"message": "Durchsuche {count} {count, plural, one {externes Projekt} other {externe Projekte}}..."
},
"project.settings.title": {
"message": "Einstellungen"
@@ -4055,48 +3923,6 @@
"settings.account.security.email.title": {
"message": "E-Mail"
},
"settings.account.security.passkey.add": {
"message": "Passkey hinzufügen"
},
"settings.account.security.passkey.add-modal.name.description": {
"message": "Stell sicher, dass du etwas nimmst, was du dir gut merken kannst, damit du den Passkey später identifizieren kannst."
},
"settings.account.security.passkey.add-modal.name.label": {
"message": "Name"
},
"settings.account.security.passkey.add-modal.name.placeholder": {
"message": "Mein Passkey"
},
"settings.account.security.passkey.description": {
"message": "Verwalte deine gespeicherten Passkeys oder füge einen neuen hinzu."
},
"settings.account.security.passkey.modal.added": {
"message": "Hinzugefügt {ago}"
},
"settings.account.security.passkey.modal.last-used": {
"message": "Zuletzt benutzt {ago}"
},
"settings.account.security.passkey.modal.loading": {
"message": "Passkeys laden..."
},
"settings.account.security.passkey.modal.never-used": {
"message": "Nie benutzt"
},
"settings.account.security.passkey.modal.no-passkeys": {
"message": "Du hast keine registrierten Passkeys."
},
"settings.account.security.passkey.remove.description": {
"message": "Dadurch wird der Passkey „{name}“ endgültig gelöscht. Du kannst dich mit ihm nicht mehr anmelden."
},
"settings.account.security.passkey.remove.title": {
"message": "Möchtest du diesen Passkey wirklich löschen?"
},
"settings.account.security.passkey.rename-modal.header": {
"message": "Passkey umbenennen"
},
"settings.account.security.passkey.title": {
"message": "Passkeys verwalten"
},
"settings.account.security.password.action.add": {
"message": "Passwort hinzufügen"
},
@@ -4709,48 +4535,6 @@
"ui.newsletter-button.tooltip": {
"message": "Abonniere den Modrinth Newsletter"
},
"version.all-versions": {
"message": "Alle Versionen"
},
"version.confirm-delete.description": {
"message": "Diese Version wird endgültig gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden."
},
"version.confirm-delete.proceed": {
"message": "Version löschen"
},
"version.confirm-delete.title": {
"message": "Willst du diese Version wirklich löschen?"
},
"version.dependency.view-project": {
"message": "Projekt anzeigen"
},
"version.dependency.view-version": {
"message": "Version anzeigen"
},
"version.download.download-dependency": {
"message": "Abhängigkeit herunterladen"
},
"version.download.no-primary-file": {
"message": "Fehler: Keine primäre Datei gefunden"
},
"version.download.optional-resource-pack": {
"message": "Optionales Ressourcenpaket"
},
"version.download.required-resource-pack": {
"message": "Erforderliches Ressourcenpaket"
},
"version.edit.button": {
"message": "Bearbeiten"
},
"version.edit.details": {
"message": "Details bearbeiten"
},
"version.edit.files": {
"message": "Dateien bearbeiten"
},
"version.edit.metadata": {
"message": "Metadaten bearbeiten"
},
"version.environment.none.description": {
"message": "Die Umgebung für diese Version wurde nicht angegeben."
},
@@ -4762,56 +4546,5 @@
},
"version.environment.unknown.title": {
"message": "Unbekannte Umgebung"
},
"version.package-as-mod.button": {
"message": "Als Mod verpacken"
},
"version.package-as-mod.description": {
"message": "Dadurch wird eine neue Version mit Unterstützung für die ausgewählten Mod-Loader erstellt. Du wirst zur neuen Version weitergeleitet und kannst diese nach deinen Wünschen bearbeiten."
},
"version.package-as-mod.header": {
"message": "Datenpaket als Mod packen"
},
"version.package-as-mod.mod-loaders": {
"message": "Modloader"
},
"version.package-as-mod.mod-loaders.description": {
"message": "Die Modloader, für die du dein Datenpaket packen möchtest."
},
"version.package-as-mod.mod-loaders.placeholder": {
"message": "Modloader auswählen..."
},
"version.package-as-mod.submit-button": {
"message": "Datenpaket packen"
},
"version.section.content.dev-info": {
"message": "Informationen für Entwickler"
},
"version.section.content.dev-info.gradle-snippet": {
"message": "build.gradle:"
},
"version.section.content.dev-info.maven-coordinates": {
"message": "Maven-Koordinaten:"
},
"version.section.content.dev-info.maven-description": {
"message": "Projekte auf Modrinth sind automatisch über ein Maven-Repository für die Verwendung mit JVM-Build-Tools wie <gradle-link>Gradle</gradle-link> verfügbar. Um mehr über die Maven-API von Modrinth zu erfahren, <article-link>klicke hier</article-link>."
},
"version.section.content.dev-info.maven-note": {
"message": "Hinweis: Falls verfügbar, solltest du stattdessen das Maven-Repository des Entwicklers verwenden, da dieses Informationen zu transitiven Abhängigkeiten enthält, die in der Maven-API von Modrinth fehlen. Außerdem kann es zu doppelten Abhängigkeiten kommen, wenn du für deine Abhängigkeiten sowohl Modrinth- als auch andere Maven-Repositorys verwendest, da die Gruppen-ID bei der Bereitstellung über die Maven-API von Modrinth abweicht."
},
"version.section.content.dev-info.version-id": {
"message": "Versions-ID:"
},
"version.supplementary-resources.copy-hash-sha1": {
"message": "SHA-1 kopieren"
},
"version.supplementary-resources.copy-hash-sha512": {
"message": "SHA-512 kopieren"
},
"version.unknown-embedded-content.description": {
"message": "Diese Version wird derzeit zurückgehalten und ist nicht öffentlich gelistet. Bitte lege Nachweise vor, dass du zur Verbreitung bestimmter darin enthaltener Dateien berechtigt bist."
},
"version.unknown-embedded-content.title": {
"message": "Wegen unbekannten eingebetteten Inhalten zurückgehalten"
}
}
+62 -329
View File
@@ -23,15 +23,6 @@
"analytics.breakdown.country": {
"message": "Land"
},
"analytics.breakdown.dependent-on": {
"message": "Abhängig von"
},
"analytics.breakdown.dependent-project-download": {
"message": "Abhängiges Projekt"
},
"analytics.breakdown.dependent-project-type": {
"message": "Abhängiger Projekttyp"
},
"analytics.breakdown.download-reason": {
"message": "Downloadgrund"
},
@@ -47,9 +38,6 @@
"analytics.breakdown.loader": {
"message": "Loader"
},
"analytics.breakdown.members": {
"message": "Mitglied"
},
"analytics.breakdown.monetization": {
"message": "Monetarisierung"
},
@@ -72,7 +60,7 @@
"message": "Alle anzeigen"
},
"analytics.chart.action.show-limited": {
"message": "Eingeschränkt anzeigen"
"message": "Zeige limitierte"
},
"analytics.chart.action.show-top-eight": {
"message": "Top 8 anzeigen"
@@ -87,13 +75,13 @@
"message": "Anmerkungen"
},
"analytics.chart.controls.aria": {
"message": "Einstellungselemente für Analysediagramm, {activeCount}"
"message": "Steuerelemente für Analysediagramm, {activeCount}"
},
"analytics.chart.controls.button": {
"message": "Einstellungen"
},
"analytics.chart.controls.dialog-aria": {
"message": "Einstellungselemente für Analysediagramm"
"message": "Steuerelemente für Analysediagramm"
},
"analytics.chart.controls.display": {
"message": "Anzeigen"
@@ -146,24 +134,6 @@
"analytics.chart.render-limit.header": {
"message": "Alle {count} Zeilen im Graphen anzeigen?"
},
"analytics.chart.table-selection.all": {
"message": "Zeige alle {itemType, select, project {{count, plural, one {Projekt} other {Projekte}}} country {{count, plural, one {Land} other {Länder}}} monetization {{count, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{count, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{count, plural, one {Downloadgrund} other {Downloadgründe}}} member {{count, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{count, plural, one {Projektversion} other {Projektversionen}}} loader {{count, plural, one {Loader} other {Loader}}} gameVersion {{count, plural, one {Spielversion} other {Spielversionen}}} other {{count, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.table-selection.count": {
"message": "Zeige {count} {itemType, select, project {{count, plural, one {Projekt} other {Projekte}}} country {{count, plural, one {Land} other {Länder}}} monetization {{count, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{count, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{count, plural, one {Downloadgrund} other {Downloadgründe}}} member {{count, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{count, plural, one {Projektversion} other {Projektversionen}}} loader {{count, plural, one {Loader} other {Loader}}} gameVersion {{count, plural, one {Spielversion} other {Spielversionen}}} other {{count, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.table-selection.limited": {
"message": "Zeige {limit} {itemType, select, project {{limit, plural, one {Projekt} other {Projekte}}} country {{limit, plural, one {Land} other {Länder}}} monetization {{limit, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{limit, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{limit, plural, one {Downloadgrund} other {Downloadgründe}}} member {{limit, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{limit, plural, one {Projektversion} other {Projektversionen}}} loader {{limit, plural, one {Loader} other {Loader}}} gameVersion {{limit, plural, one {Spielversion} other {Spielversionen}}} other {{limit, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.table-selection.top": {
"message": "Zeige die top {count} {itemType, select, project {{count, plural, one {Projekt} other {Projekte}}} country {{count, plural, one {Land} other {Länder}}} monetization {{count, plural, one {Monetarisierungswert} other {Monetarisierungswerte}}} downloadSource {{count, plural, one {Downloadquelle} other {Downloadquellen}}} downloadReason {{count, plural, one {Downloadgrund} other {Downloadgründe}}} member {{count, plural, one {Mitglied} other {Mitglieder}}} projectVersion {{count, plural, one {Projektversion} other {Projektversionen}}} loader {{count, plural, one {Loader} other {Loader}}} gameVersion {{count, plural, one {Spielversion} other {Spielversionen}}} other {{count, plural, one {Artikel} other {Artikel}}}} aus der Tabelle"
},
"analytics.chart.tooltip.dependent-on-project": {
"message": "Abhängig von {project}"
},
"analytics.chart.tooltip.dependent-project-version": {
"message": "{dependentProject} abhängig von {dependencyProject}, {version}"
},
"analytics.chart.tooltip.duration.days": {
"message": "{count, plural, one {# Tag} other {# Tage}}"
},
@@ -248,15 +218,9 @@
"analytics.filter.search.countries": {
"message": "Länder durchsuchen..."
},
"analytics.filter.search.dependent-projects": {
"message": "Projekte durchsuchen..."
},
"analytics.filter.search.download-sources": {
"message": "Download-Quellen durchsuchen..."
},
"analytics.filter.search.members": {
"message": "Mitglieder durchsuchen..."
},
"analytics.filter.search.project-versions": {
"message": "Projektversionen durchsuchen..."
},
@@ -503,12 +467,6 @@
"analytics.value.monetized": {
"message": "Monetarisiert"
},
"analytics.value.no-dependent": {
"message": "Keine"
},
"analytics.value.no-dependent-tooltip": {
"message": "Aus anderen Gründen als einer Abhängigkeit heruntergeladen"
},
"analytics.value.none": {
"message": "Keine"
},
@@ -731,72 +689,12 @@
"auth.authorize.authorize-app-name": {
"message": "Autorisiere {appName}"
},
"auth.authorize.error.missing-parameters": {
"message": "Erforderliche Abfrageparameter für OAuth fehlen."
},
"auth.authorize.error.no-redirect-url": {
"message": "Keine Weiterleitungsziel in Antwort gefunden"
},
"auth.authorize.errro-title": {
"message": "Ein Fehler ist aufgetreten"
},
"auth.authorize.redirect-url": {
"message": "Du wirst zu <redirect-url>{url}</redirect-url> weitergeleitet"
},
"auth.continue-with-provider": {
"message": "Weiter mit {provider}"
},
"auth.create-account.age-requirement.warning-title": {
"message": "Mindestalter"
},
"auth.create-account.complete-sign-up": {
"message": "Registrierung abschließen"
},
"auth.create-account.date-of-birth.invalid.text": {
"message": "Bitte gib ein gültiges Geburtsdatum ein. Das Jahr darf nicht 0000 sein."
},
"auth.create-account.date-of-birth.invalid.title": {
"message": "Ungültiges Geburtsdatum"
},
"auth.create-account.date-of-birth.label": {
"message": "Geburtsdatum"
},
"auth.create-account.date-of-birth.placeholder": {
"message": "Wähle dein Geburtsdatum"
},
"auth.create-account.date-of-birth.required.text": {
"message": "Bitte gib dein Geburtsdatum ein, bevor du fortfährst."
},
"auth.create-account.date-of-birth.required.title": {
"message": "Geburtsdatum erforderlich"
},
"auth.create-account.date-of-birth.under13-helper": {
"message": "Du erfüllst das Mindestalter für die Erstellung eines Kontos bei Modrinth nicht."
},
"auth.create-account.info-panel.source-code-link": {
"message": "Relevanter Quellcode"
},
"auth.create-account.info-panel.text": {
"message": "Wir speichern dein Geburtsdatum nicht. Wir verwenden es ausschließlich bei der Registrierung, um die Einhaltung der COPPA-Vorschriften sicherzustellen."
},
"auth.create-account.page-title": {
"message": "Konto erstellen"
},
"auth.create-account.security-check.label": {
"message": "Sicherheitscheck"
},
"auth.create-account.subscribe.label": {
"message": "Haltet mich per E-Mail über coole Dinge auf dem Laufenden, an denen Modrinth gerade arbeitet"
},
"auth.create-account.title": {
"message": "Ein Konto erstellen"
},
"auth.create-account.username.label": {
"message": "Benutzername"
},
"auth.create-account.username.placeholder": {
"message": "Benutzername eingeben"
},
"auth.reset-password.method-choice.action": {
"message": "E-Mail zur Wiederherstellung senden"
},
@@ -831,53 +729,23 @@
"message": "Dein Passwort zurücksetzen"
},
"auth.sign-in.2fa.description": {
"message": "Gib den 6-stelligen Code aus deiner Authentifikator-App oder einen deiner Backup-Codes ein."
"message": "Bitte gib einen Zwei-Faktor-Code ein, um fortzufahren."
},
"auth.sign-in.2fa.label": {
"message": "Zwei-Faktor-Authentifizierung"
"message": "Zwei-Faktor-Code eingeben"
},
"auth.sign-in.2fa.placeholder": {
"message": "Code eingeben..."
},
"auth.sign-in.continue-with-email": {
"message": "Weiter mit E-Mail"
},
"auth.sign-in.continue-with-passkey": {
"message": "Weiter mit Passkey"
},
"auth.sign-in.create-account": {
"message": "Registrieren"
},
"auth.sign-in.forgot-password": {
"message": "Passwort vergessen"
},
"auth.sign-in.last-sign-in": {
"message": "Zuletzt benutzt"
},
"auth.sign-in.no-account": {
"message": "Du hast kein Konto?"
},
"auth.sign-in.sign-in-with": {
"message": "Bei Modrinth anmelden"
"message": "Anmelden mit"
},
"auth.sign-in.title": {
"message": "Anmelden"
},
"auth.sign-up.age-requirement.warning-title": {
"message": "Mindestalter"
},
"auth.sign-up.continue-with-email": {
"message": "Mit E-Mail fortfahren"
},
"auth.sign-up.legal-dislaimer": {
"message": "Durch die Erstellung eines Kontos stimmst du den <terms-link>Nutzungsbedingungen</terms-link> und der <privacy-policy-link>Datenschutzerklärung</privacy-policy-link> von Modrinth zu."
},
"auth.sign-up.show-fewer-options": {
"message": "Zeige weniger Optionen"
},
"auth.sign-up.show-other-options": {
"message": "Zeige weitere Optionen"
},
"auth.sign-up.sign-in-option.title": {
"message": "Du hast bereits ein Konto?"
},
@@ -885,14 +753,11 @@
"message": "Registrieren"
},
"auth.sign-up.title.sign-up-with": {
"message": "Ein Konto erstellen"
"message": "Registrieren mit"
},
"auth.verify-email.action.account-settings": {
"message": "Kontoeinstellungen"
},
"auth.verify-email.action.discover-mods": {
"message": "Mods entdecken"
},
"auth.verify-email.already-verified.description": {
"message": "Deine E-Mail wurde bereits bestätigt!"
},
@@ -911,15 +776,6 @@
"auth.verify-email.failed-verification.title": {
"message": "E-Mail-Verifizierung fehlgeschlagen"
},
"auth.verify-email.notification.email-sent.description": {
"message": "Eine E-Mail mit einem Link zur Verifizierung deines Accounts wurde an {email} gesendet."
},
"auth.verify-email.notification.email-sent.title": {
"message": "E-Mail gesendet"
},
"auth.verify-email.notification.error-occurred.title": {
"message": "Ein Fehler ist aufgetreten"
},
"auth.verify-email.post-verification.description": {
"message": "Deine E-Mail-Adresse wurde erfolgreich bestätigt!"
},
@@ -1634,6 +1490,9 @@
"dashboard.overview.notifications.loading": {
"message": "Benachrichtigungen werden geladen..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Du kannst mehrere Projekte gleichzeitig bearbeiten, indem du sie unten auswählst."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Serverprojekte unterstützen Massenbearbeitung nicht"
},
@@ -1709,6 +1568,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Bitte überprüfe die Umgebungsmetadaten"
},
"dashboard.projects.sort.ascending": {
"message": "Aufsteigend"
},
"dashboard.projects.sort.descending": {
"message": "Absteigend"
},
"dashboard.projects.sort.option.name": {
"message": "Name"
},
"dashboard.projects.sort.option.status": {
"message": "Status"
},
"dashboard.projects.sort.option.type": {
"message": "Typ"
},
"dashboard.projects.table.icon": {
"message": "Icon"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -3221,21 +3098,45 @@
"project.details.licensed": {
"message": "Lizenziert unter"
},
"project.download.game-version": {
"message": "Spielversion: {version}"
},
"project.download.game-version-error": {
"message": "Fehler: Keine Spielversion gefunden"
},
"project.download.game-version-tooltip": {
"message": "{title} ist nur für {version} verfügbar"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} unterstützt {gameVersion} für {platform} nicht"
},
"project.download.install-with-app": {
"message": "Mit Modrinth App installieren"
},
"project.download.no-app": {
"message": "Du hast die Modrinth App nicht?"
},
"project.download.no-versions-available": {
"message": "Keine Versionen für {gameVersion} und {platform} verfügbar."
},
"project.download.platform": {
"message": "Plattform: {platform}"
},
"project.download.platform-error": {
"message": "Fehler: Keine Platform gefunden"
},
"project.download.platform-tooltip": {
"message": "{title} ist nur für {platform} verfügbar"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} unterstützt {platform} für die {gameVersion} nicht"
},
"project.download.search-game-versions": {
"message": "Spielversionen durchsuchen..."
},
"project.download.search-game-versions-label": {
"message": "Spielversionen durchuschen..."
},
"project.download.select-game-version": {
"message": "Spielversion auswählen"
},
@@ -3426,76 +3327,43 @@
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung folgender Dateien hast. Danach werden die zurückgehaltenen Versionen automatisch veröffentlicht."
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung folgender Dateien hast; alle zurückgehaltenen Versionen werden automatisch veröffentlicht."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung aller folgenden Dateien hast, bevor du dein Projekt zur Überprüfung einreichst."
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung aller folgenden Dateien hast, bevor du dein Projekt zur Überprüfung einreichen kannst."
},
"project.settings.permissions.attention-needed.title": {
"message": "Unbekannter externer Inhalt"
},
"project.settings.permissions.bad-proof.description": {
"message": "Das Moderationsteam von Modrinth hat die von dir für einige externe Inhalte vorgelegten Berechtigungsnachweise abgelehnt. Bitte überprüfe die unten aufgeführten abgelehnten Einträge und lege zulässige Nachweise vor oder entfernen die Inhalte."
},
"project.settings.permissions.bad-proof.title": {
"message": "Einige Nachweise wurden abgelehnt"
},
"project.settings.permissions.collapse-all": {
"message": "Alle ausblenden"
"message": "Unbekannter eingebetteter Inhalt"
},
"project.settings.permissions.completed.description": {
"message": "Alle externen Inhalte sind mit Erlaubnisangaben versehen, und die Quellenangaben wurden angegeben."
"message": "Alle externen Inhalte haben Quellenangaben."
},
"project.settings.permissions.completed.title": {
"message": "Genehmigungen abgeschlossen!"
"message": "Quellenangaben vollständig!"
},
"project.settings.permissions.empty-state.description": {
"message": "Keine der Versionen deines Projekts enthält externe Inhalte, weshalb du dir keine Gedanken über die Einholung von Genehmigungen machen musst."
"message": "Keine deiner Versionen enthält externe Inhalte, du musst du dir keine Gedanken über die Einholung von Genehmigungen machen."
},
"project.settings.permissions.empty-state.heading": {
"message": "Du bist startklar!"
},
"project.settings.permissions.expand-all": {
"message": "Alle anzeigen"
},
"project.settings.permissions.fail.description": {
"message": "Möglicherweise verfügst du nicht über die erforderliche Genehmigung zur Weitergabe einiger externer Inhalte in deinem Projekt. Um dein Projekt auf Modrinth zu veröffentlichen, entferne bitte diese Inhalte oder lege einen Nachweis vor, dass du über die erforderliche Genehmigung zur Nutzung verfügst."
"message": "Du hast keine Berechtigung, einige der von dir hinzugefügten externen Inhalte weiterzuverbreiten. Entferne die urheberrechtsverletzenden Inhalte, um auf Modrinth veröffentlichen zu können."
},
"project.settings.permissions.fail.title": {
"message": "Bestimmte Inhalte können nicht enthalten sein"
},
"project.settings.permissions.info-banner.description": {
"message": "Wenn du Inhalte einbindest, welche nicht auf Modrinth gehostet werden, musst du uns deren Quelle mitteilen und bestätigen, dass du die Berechtigung zur Verbreitung der Dateien hast. Schau dir <link>unsere Ankündigung zu diesem neuen System</link> an, um mehr zu erfahren!"
"message": "Wenn du Inhalte einbindest, welche nicht auf Modrinth gehostet werden, musst du uns deren Quelle mitteilen und bestätigen, dass du die Berechtigung zur Verbreitung der Dateien hast. In <link>unserer Anleitung</link> lernst du, wie du dies am besten machst!"
},
"project.settings.permissions.info-banner.title": {
"message": "Erfahre mehr über die Rechte zur Verbreitung"
"message": "So funktionieren Quellenangaben"
},
"project.settings.permissions.learn-more": {
"message": "Mehr erfahren"
},
"project.settings.permissions.no-results": {
"message": "Keine externen Dateien entsprechen deiner Suche."
},
"project.settings.permissions.not-allowed.description": {
"message": "Einige der enthaltenen externen Inhalte dürfen auf Modrinth nicht verbreitet werden, da sie gegen unsere Inhaltsrichtlinien verstoßen und daher entfernt werden müssen."
},
"project.settings.permissions.pending-approval-count": {
"message": "{count, plural, =0 {Keine Quellenangaben müssen genehmigt werden} one {# Quellenangabe muss genehmigt werden} other {# Quellenangaben müssen genehmigt werden}}"
},
"project.settings.permissions.search-placeholder": {
"message": "Durchsuche {count} {count, plural, one {Projekt} other {Projekten}}..."
},
"project.settings.permissions.sort.most-files": {
"message": "Meiste Dateien"
},
"project.settings.permissions.sort.recently-edited": {
"message": "Kürzlich bearbeitet"
},
"project.settings.permissions.sort.rejected": {
"message": "Abgelehnt"
},
"project.settings.permissions.sort.status": {
"message": "Status"
"message": "Durchsuche {count} {count, plural, one {externes Projekt} other {externe Projekte}}..."
},
"project.settings.title": {
"message": "Einstellungen"
@@ -4055,48 +3923,6 @@
"settings.account.security.email.title": {
"message": "E-Mail"
},
"settings.account.security.passkey.add": {
"message": "Passkey hinzufügen"
},
"settings.account.security.passkey.add-modal.name.description": {
"message": "Stell sicher, dass du etwas nimmst, was du dir gut merken kannst, damit du den Passkey später identifizieren kannst."
},
"settings.account.security.passkey.add-modal.name.label": {
"message": "Name"
},
"settings.account.security.passkey.add-modal.name.placeholder": {
"message": "Mein Passkey"
},
"settings.account.security.passkey.description": {
"message": "Verwalte deine gespeicherten Passkeys oder füge einen neuen hinzu."
},
"settings.account.security.passkey.modal.added": {
"message": "Vor {ago} hinzugefügt"
},
"settings.account.security.passkey.modal.last-used": {
"message": "Zuletzt {ago} verwendet"
},
"settings.account.security.passkey.modal.loading": {
"message": "Passkeys werden geladen…"
},
"settings.account.security.passkey.modal.never-used": {
"message": "Nie verwendet"
},
"settings.account.security.passkey.modal.no-passkeys": {
"message": "Du hast keine registrierten Passkeys."
},
"settings.account.security.passkey.remove.description": {
"message": "Dadurch wird der Passkey „{name}“ endgültig gelöscht. Du kannst dich mit ihm nicht mehr anmelden."
},
"settings.account.security.passkey.remove.title": {
"message": "Möchtest du diesen Passkey wirklich löschen?"
},
"settings.account.security.passkey.rename-modal.header": {
"message": "Passkey umbenennen"
},
"settings.account.security.passkey.title": {
"message": "Passkeys verwalten"
},
"settings.account.security.password.action.add": {
"message": "Passwort hinzufügen"
},
@@ -4709,48 +4535,6 @@
"ui.newsletter-button.tooltip": {
"message": "Abonniere den Modrinth-Newsletter"
},
"version.all-versions": {
"message": "Alle Versionen"
},
"version.confirm-delete.description": {
"message": "Diese Version wird endgültig gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden."
},
"version.confirm-delete.proceed": {
"message": "Version löschen"
},
"version.confirm-delete.title": {
"message": "Möchtest du diese Version wirklich löschen?"
},
"version.dependency.view-project": {
"message": "Projekt anzeigen"
},
"version.dependency.view-version": {
"message": "Version anzeigen"
},
"version.download.download-dependency": {
"message": "Abhängigkeit herunterladen"
},
"version.download.no-primary-file": {
"message": "Fehler: Keine primäre Datei gefunden"
},
"version.download.optional-resource-pack": {
"message": "Optionales Ressourcenpaket"
},
"version.download.required-resource-pack": {
"message": "Erforderliches Ressourcenpaket"
},
"version.edit.button": {
"message": "Bearbeiten"
},
"version.edit.details": {
"message": "Details bearbeiten"
},
"version.edit.files": {
"message": "Dateien bearbeiten"
},
"version.edit.metadata": {
"message": "Metadaten bearbeiten"
},
"version.environment.none.description": {
"message": "Die Umgebung für diese Version wurde nicht angegeben."
},
@@ -4762,56 +4546,5 @@
},
"version.environment.unknown.title": {
"message": "Unbekannte Umgebung"
},
"version.package-as-mod.button": {
"message": "Als Mod verpacken"
},
"version.package-as-mod.description": {
"message": "Dadurch wird eine neue Version mit Unterstützung für die ausgewählten Mod-Loader erstellt. Du wirst zur neuen Version weitergeleitet und kannst diese nach deinen Wünschen bearbeiten."
},
"version.package-as-mod.header": {
"message": "Datenpaket als Mod packen"
},
"version.package-as-mod.mod-loaders": {
"message": "Modloader"
},
"version.package-as-mod.mod-loaders.description": {
"message": "Die Modloader, für die du dein Datenpaket packen möchtest."
},
"version.package-as-mod.mod-loaders.placeholder": {
"message": "Modloader auswählen..."
},
"version.package-as-mod.submit-button": {
"message": "Datenpaket packen"
},
"version.section.content.dev-info": {
"message": "Informationen für Entwickler"
},
"version.section.content.dev-info.gradle-snippet": {
"message": "build.gradle:"
},
"version.section.content.dev-info.maven-coordinates": {
"message": "Maven-Koordinaten:"
},
"version.section.content.dev-info.maven-description": {
"message": "Projekte auf Modrinth sind automatisch über ein Maven-Repository für die Verwendung mit JVM-Build-Tools wie <gradle-link>Gradle</gradle-link> verfügbar. Um mehr über die Maven-API von Modrinth zu erfahren, <article-link>klicke hier</article-link>."
},
"version.section.content.dev-info.maven-note": {
"message": "Hinweis: Falls verfügbar, solltest du stattdessen das Maven-Repository des Entwicklers verwenden, da dieses Informationen zu transitiven Abhängigkeiten enthält, die in der Maven-API von Modrinth fehlen. Außerdem kann es zu doppelten Abhängigkeiten kommen, wenn du für deine Abhängigkeiten sowohl Modrinth- als auch andere Maven-Repositorys verwendest, da die Gruppen-ID bei der Bereitstellung über die Maven-API von Modrinth abweicht."
},
"version.section.content.dev-info.version-id": {
"message": "Versions-ID:"
},
"version.supplementary-resources.copy-hash-sha1": {
"message": "SHA-1 kopieren"
},
"version.supplementary-resources.copy-hash-sha512": {
"message": "SHA-512 kopieren"
},
"version.unknown-embedded-content.description": {
"message": "Diese Version wird derzeit zurückgehalten und ist nicht öffentlich gelistet. Bitte lege Nachweise vor, dass du zur Verbreitung bestimmter darin enthaltener Dateien berechtigt bist."
},
"version.unknown-embedded-content.title": {
"message": "Wegen unbekannten eingebetteten Inhalten zurückgehalten"
}
}
+42 -129
View File
@@ -518,9 +518,6 @@
"analytics.value.unknown": {
"message": "Unknown"
},
"analytics.value.unknown-dependent-tooltip": {
"message": "There's no metadata to determine which dependent project this download attributes to."
},
"analytics.value.unmonetized": {
"message": "Unmonetized"
},
@@ -1637,6 +1634,9 @@
"dashboard.overview.notifications.loading": {
"message": "Loading notifications..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "You can edit multiple projects at once by selecting them below."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Server projects do not support bulk editing"
},
@@ -1712,11 +1712,23 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Please review environment metadata"
},
"dashboard.projects.selection-bar.aria-label": {
"message": "Selected projects actions"
"dashboard.projects.sort.ascending": {
"message": "Ascending"
},
"dashboard.projects.selection-bar.selected-count": {
"message": "{count, plural, one {# project selected} other {# projects selected}}"
"dashboard.projects.sort.descending": {
"message": "Descending"
},
"dashboard.projects.sort.option.name": {
"message": "Name"
},
"dashboard.projects.sort.option.status": {
"message": "Status"
},
"dashboard.projects.sort.option.type": {
"message": "Type"
},
"dashboard.projects.table.icon": {
"message": "Icon"
},
"dashboard.projects.table.id": {
"message": "ID"
@@ -1724,9 +1736,6 @@
"dashboard.projects.table.name": {
"message": "Name"
},
"dashboard.projects.table.select-all-bulk-editable": {
"message": "Select all projects that support bulk editing"
},
"dashboard.projects.table.status": {
"message": "Status"
},
@@ -2672,9 +2681,6 @@
"layout.nav.upgrade-to-modrinth-plus": {
"message": "Upgrade to Modrinth+"
},
"moderation.exclude-technical-review": {
"message": "Exclude TR"
},
"moderation.moderate": {
"message": "Moderate"
},
@@ -2690,45 +2696,6 @@
"moderation.page.technicalReview": {
"message": "Tech review"
},
"modpack-scan-modal.failed": {
"message": "Failed"
},
"modpack-scan-modal.load-versions-error": {
"message": "Failed to load versions: {error}"
},
"modpack-scan-modal.loading-versions": {
"message": "Loading versions..."
},
"modpack-scan-modal.new-files": {
"message": "New Files"
},
"modpack-scan-modal.new-groups": {
"message": "New Groups"
},
"modpack-scan-modal.no-files": {
"message": "No files found."
},
"modpack-scan-modal.not-scanned": {
"message": "Not scanned"
},
"modpack-scan-modal.override-files": {
"message": "Override Files ({count})"
},
"modpack-scan-modal.pack-file-name": {
"message": "Pack File Name"
},
"modpack-scan-modal.scan-all-files": {
"message": "Scan All Files"
},
"modpack-scan-modal.scan-error": {
"message": "Some files failed to scan: {error}"
},
"modpack-scan-modal.scanning": {
"message": "Scanning..."
},
"modpack-scan-modal.title": {
"message": "Modpack Scan ({scanned}/{total} Files)"
},
"muralpay.account-type.checking": {
"message": "Checking"
},
@@ -3275,80 +3242,44 @@
"project.details.licensed": {
"message": "Licensed"
},
"project.download.additional-files-title": {
"message": "Additional files"
"project.download.game-version": {
"message": "Game version: {version}"
},
"project.download.base-game-version-incompatible-tooltip": {
"message": "This game version is incompatible with the base project."
"project.download.game-version-error": {
"message": "Error: no game versions found"
},
"project.download.base-loader-incompatible-tooltip": {
"message": "This loader is incompatible with the base project."
},
"project.download.compatible-version-title": {
"message": "Compatible version"
},
"project.download.dependencies-title": {
"message": "Dependencies"
},
"project.download.dependency-already-installed": {
"message": "This dependency is already installed"
},
"project.download.dependency-conflicting": {
"message": "This dependency conflicts with another dependency"
},
"project.download.dependency-download-file": {
"message": "Download {filename}"
},
"project.download.dependency-download-file-with-size": {
"message": "Download {filename} ({size})"
},
"project.download.dependency-duplicate": {
"message": "This dependency is already included"
},
"project.download.dependency-missing-version": {
"message": "This dependency version is unavailable"
},
"project.download.dependency-no-compatible-version": {
"message": "No compatible version is available for this dependency"
},
"project.download.dependency-quilt-fabric-api": {
"message": "Fabric API is skipped for Quilt"
},
"project.download.dependency-unavailable": {
"message": "This dependency cannot be downloaded"
},
"project.download.download-version": {
"message": "Download {version}"
},
"project.download.duplicate-dependencies-hidden": {
"message": "Duplicate dependencies are hidden"
},
"project.download.file-unavailable": {
"message": "This file cannot be downloaded"
"project.download.game-version-tooltip": {
"message": "{title} is only available for {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} does not support {gameVersion} for {platform}"
},
"project.download.install-with-app-description": {
"message": "Automatically install the correct version and dependencies."
},
"project.download.manually": {
"message": "Download manually"
"project.download.install-with-app": {
"message": "Install with Modrinth App"
},
"project.download.no-app": {
"message": "Don't have Modrinth App?"
},
"project.download.no-game-versions-found": {
"message": "No game versions found"
},
"project.download.no-versions-available": {
"message": "No versions available for {gameVersion} and {platform}."
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Error: no platforms found"
},
"project.download.platform-tooltip": {
"message": "{title} is only available for {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} does not support {platform} for {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Select game version"
"message": "Search game versions..."
},
"project.download.search-game-versions-label": {
"message": "Search game versions..."
},
"project.download.select-game-version": {
"message": "Select game version"
@@ -3356,27 +3287,12 @@
"project.download.select-platform": {
"message": "Select platform"
},
"project.download.selected-version-download-all": {
"message": "Download all (.zip)"
},
"project.download.selected-version-downloading": {
"message": "Downloading... ({current}/{total})"
},
"project.download.selected-version-failed-text": {
"message": "One or more version files could not be downloaded. Please try again."
},
"project.download.selected-version-failed-title": {
"message": "Could not download version"
},
"project.download.show-all-versions": {
"message": "Show all versions"
},
"project.download.title": {
"message": "Download {title}"
},
"project.download.unknown-loader": {
"message": "Unknown loader"
},
"project.environment.migration-no-permission.message": {
"message": "We've just overhauled the Environments system on Modrinth and new options are now available. You don't have permission to modify these settings, but please let another member of the project know that the environment metadata needs to be verified."
},
@@ -4206,7 +4122,7 @@
"message": "Last used {ago}"
},
"settings.account.security.passkey.modal.loading": {
"message": "Loading passkeys..."
"message": "Loading passkeys"
},
"settings.account.security.passkey.modal.never-used": {
"message": "Never used"
@@ -4857,7 +4773,7 @@
"message": "View version"
},
"version.download.download-dependency": {
"message": "Select a version to download"
"message": "Download dependency"
},
"version.download.no-primary-file": {
"message": "Error: No primary file found"
@@ -4868,9 +4784,6 @@
"version.download.required-resource-pack": {
"message": "Required resource pack"
},
"version.download.version": {
"message": "Download {version} ({size})"
},
"version.edit.button": {
"message": "Edit"
},
+59 -284
View File
@@ -731,72 +731,12 @@
"auth.authorize.authorize-app-name": {
"message": "Autorizar {appName}"
},
"auth.authorize.error.missing-parameters": {
"message": "Faltan los parámetros de consulta OAuth obligatorios."
},
"auth.authorize.error.no-redirect-url": {
"message": "No se encontró una ubicación de redirección en la respuesta"
},
"auth.authorize.errro-title": {
"message": "Se produjo un error"
},
"auth.authorize.redirect-url": {
"message": "Serás redirigido a <redirect-url>{url}</redirect-url>"
},
"auth.continue-with-provider": {
"message": "Continuar con {provider}"
},
"auth.create-account.age-requirement.warning-title": {
"message": "Requisito de edad"
},
"auth.create-account.complete-sign-up": {
"message": "Completar el registro"
},
"auth.create-account.date-of-birth.invalid.text": {
"message": "Por favor, ingresa una fecha de nacimiento válida. El año no puede ser 0000."
},
"auth.create-account.date-of-birth.invalid.title": {
"message": "Fecha de nacimiento no válida"
},
"auth.create-account.date-of-birth.label": {
"message": "Fecha de nacimiento"
},
"auth.create-account.date-of-birth.placeholder": {
"message": "Selecciona tu fecha de nacimiento"
},
"auth.create-account.date-of-birth.required.text": {
"message": "Por favor, ingresa tu fecha de nacimiento antes de continuar."
},
"auth.create-account.date-of-birth.required.title": {
"message": "Se requiere la fecha de nacimiento"
},
"auth.create-account.date-of-birth.under13-helper": {
"message": "No cumples con el requisito de edad para crear una cuenta en Modrinth."
},
"auth.create-account.info-panel.source-code-link": {
"message": "Código fuente relevante"
},
"auth.create-account.info-panel.text": {
"message": "No almacenamos tu fecha de nacimiento. Solo la utilizamos durante el registro para verificar el cumplimiento de la ley COPPA."
},
"auth.create-account.page-title": {
"message": "Crear cuenta"
},
"auth.create-account.security-check.label": {
"message": "Control de seguridad"
},
"auth.create-account.subscribe.label": {
"message": "Manténme al tanto de las cosas geniales en las que está trabajando Modrinth por correo electrónico"
},
"auth.create-account.title": {
"message": "Crear una cuenta"
},
"auth.create-account.username.label": {
"message": "Nombre de usuario"
},
"auth.create-account.username.placeholder": {
"message": "Ingresa tu nombre de usuario"
},
"auth.reset-password.method-choice.action": {
"message": "Enviar correo de recuperación"
},
@@ -831,53 +771,23 @@
"message": "Restablece tu contraseña"
},
"auth.sign-in.2fa.description": {
"message": "Ingresa el código de 6 dígitos de tu aplicación de autenticación o uno de tus códigos de respaldo."
"message": "Por favor, introduce un código de verificación en dos pasos para continuar."
},
"auth.sign-in.2fa.label": {
"message": "Autenticación de dos factores"
"message": "Introduce el código de verificación en dos pasos"
},
"auth.sign-in.2fa.placeholder": {
"message": "Introduce el código..."
},
"auth.sign-in.continue-with-email": {
"message": "Continuar con el correo electrónico"
},
"auth.sign-in.continue-with-passkey": {
"message": "Continuar con la clave de acceso"
},
"auth.sign-in.create-account": {
"message": "Registrarse"
},
"auth.sign-in.forgot-password": {
"message": "Olvidé mi contraseña"
},
"auth.sign-in.last-sign-in": {
"message": "Último inicio de sesión"
},
"auth.sign-in.no-account": {
"message": "¿No tienes una cuenta?"
},
"auth.sign-in.sign-in-with": {
"message": "Inicia sesión en Modrinth"
"message": "Iniciar sesión con"
},
"auth.sign-in.title": {
"message": "Iniciar sesión"
},
"auth.sign-up.age-requirement.warning-title": {
"message": "Requisito de edad"
},
"auth.sign-up.continue-with-email": {
"message": "Continuar con el correo electrónico"
},
"auth.sign-up.legal-dislaimer": {
"message": "Al crear una cuenta, aceptas los <terms-link>Términos</terms-link> y la <privacy-policy-link>Política de privacidad</privacy-policy-link> de Modrinth."
},
"auth.sign-up.show-fewer-options": {
"message": "Mostrar menos opciones"
},
"auth.sign-up.show-other-options": {
"message": "Mostrar otras opciones"
},
"auth.sign-up.sign-in-option.title": {
"message": "¿Ya tienes una cuenta?"
},
@@ -885,14 +795,11 @@
"message": "Registrarse"
},
"auth.sign-up.title.sign-up-with": {
"message": "Crear una cuenta"
"message": "Registrarse con"
},
"auth.verify-email.action.account-settings": {
"message": "Configuración de cuenta"
},
"auth.verify-email.action.discover-mods": {
"message": "Descubre mods"
},
"auth.verify-email.already-verified.description": {
"message": "¡Tu correo ya está verificado!"
},
@@ -911,15 +818,6 @@
"auth.verify-email.failed-verification.title": {
"message": "Error al verificar el correo"
},
"auth.verify-email.notification.email-sent.description": {
"message": "Se ha enviado un correo electrónico con un enlace para verificar tu cuenta a {email}."
},
"auth.verify-email.notification.email-sent.title": {
"message": "Correo electrónico enviado"
},
"auth.verify-email.notification.error-occurred.title": {
"message": "Se produjo un error"
},
"auth.verify-email.post-verification.description": {
"message": "¡Tu correo ha sido verificado correctamente!"
},
@@ -1634,6 +1532,9 @@
"dashboard.overview.notifications.loading": {
"message": "Cargando notificaciones..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Puedes editar varios proyectos a la vez seleccionándolos a continuación."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Los proyectos no son compatible con la edición masiva de servidores"
},
@@ -1709,6 +1610,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Por favor revisa los metadatos de entorno"
},
"dashboard.projects.sort.ascending": {
"message": "Ascendente"
},
"dashboard.projects.sort.descending": {
"message": "Descendente"
},
"dashboard.projects.sort.option.name": {
"message": "Nombre"
},
"dashboard.projects.sort.option.status": {
"message": "Estado"
},
"dashboard.projects.sort.option.type": {
"message": "Tipo"
},
"dashboard.projects.table.icon": {
"message": "Icono"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -3221,21 +3140,45 @@
"project.details.licensed": {
"message": "Con licencia"
},
"project.download.game-version": {
"message": "Versión del juego: {version}"
},
"project.download.game-version-error": {
"message": "Error: no se encontraron versiones del juego"
},
"project.download.game-version-tooltip": {
"message": "{title} solo está disponible para {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} no soporta {gameVersion} para {platform}"
},
"project.download.install-with-app": {
"message": "Instalar con la Modrinth App"
},
"project.download.no-app": {
"message": "¿No tienes la Modrinth App?"
},
"project.download.no-versions-available": {
"message": "No hay versiones disponibles para {gameVersion} y {platform}."
},
"project.download.platform": {
"message": "Plataforma: {platform}"
},
"project.download.platform-error": {
"message": "Error: no se encontraron plataformas"
},
"project.download.platform-tooltip": {
"message": "{title} solo está disponible para {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} no soporta {platform} para {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Buscar versiones del juego..."
},
"project.download.search-game-versions-label": {
"message": "Buscar versiones del juego..."
},
"project.download.select-game-version": {
"message": "Selecciona la versión del juego"
},
@@ -3426,76 +3369,43 @@
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Por favor, proporcione prueba de que tiene permiso para redistribuir todos los siguientes archivos. Una vez completado, las versiones retenidas se publicarán automáticamente."
"message": "Por favor, proporciona pruebas de que tienes permiso para redistribuir todos estos archivos y cualquier versión retenida se publicará automáticamente."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Por favor, proporcione prueba de que tiene permiso para redistribuir todos los siguientes archivos antes de enviar su proyecto para revisión."
"message": "Por favor, proporciona pruebas de que tienes permiso para redistribuir todos estos archivos antes de enviar tu proyecto a revisión."
},
"project.settings.permissions.attention-needed.title": {
"message": "Contenido externo desconocido"
},
"project.settings.permissions.bad-proof.description": {
"message": "El equipo de moderación de Modrinth ha rechazado la información de permisos que proporcionaste para algún contenido externo. Por favor, revise los elementos rechazados a continuación y proporcione pruebas aceptables o elimine el contenido."
},
"project.settings.permissions.bad-proof.title": {
"message": "Algunas pruebas fueron rechazadas."
},
"project.settings.permissions.collapse-all": {
"message": "Colapsar todo"
"message": "Incluye contenido desconocido"
},
"project.settings.permissions.completed.description": {
"message": "Todo el contenido externo tiene información de permisos y se han proporcionado las atribuciones."
"message": "Todo el contenido externo proporciona atribuciones."
},
"project.settings.permissions.completed.title": {
"message": Permisos completados!"
"message": Atribuciones completadas!"
},
"project.settings.permissions.empty-state.description": {
"message": "Ninguna de las versiones de tu proyecto contiene contenido externo, así que no necesitas preocuparte por obtener permisos."
"message": "Ninguna de tus versiones presenta contenido externo, por lo que no necesitas preocuparte de obtener permisos."
},
"project.settings.permissions.empty-state.heading": {
"message": "¡Ya estás listo!"
},
"project.settings.permissions.expand-all": {
"message": "Expandir todo"
},
"project.settings.permissions.fail.description": {
"message": "Es posible que no tengas permiso para redistribuir parte del contenido externo en tu proyecto. Para publicar en Modrinth, por favor elimina este contenido o proporciona prueba de que tienes permiso para usarlo."
"message": "No tienes permiso de redistribuir parte del contenido externo que añadiste. Para poder publicar en Modrinth, quite el contenido infractor."
},
"project.settings.permissions.fail.title": {
"message": "No se puede incluir parte del contenido"
},
"project.settings.permissions.info-banner.description": {
"message": "Si incluyes contenido que no esté alojado en Modrinth, debes informarnos de dónde proviene y verificar que tienes permiso para distribuir los archivos. Consulta <link>nuestro anuncio de este nuevo sistema</link> para obtener más información."
"message": "Si incluyes contenido que no está alojado en Modrinth, debes indicarnos de dónde es y verificar que tienes permiso para distribuir los archivos. ¡Mira <link>nuestra guía</link> para saber cómo hacerlo correctamente!"
},
"project.settings.permissions.info-banner.title": {
"message": "Aprende sobre los permisos de distribución"
"message": "Aprende cómo funcionan las atribuciones"
},
"project.settings.permissions.learn-more": {
"message": "Aprender más"
},
"project.settings.permissions.no-results": {
"message": "No hay archivos externos que coincidan con tu búsqueda."
},
"project.settings.permissions.not-allowed.description": {
"message": "Parte del contenido externo incluido no puede ser distribuido en Modrinth porque viola nuestras Reglas de Contenido y debe ser eliminado."
},
"project.settings.permissions.pending-approval-count": {
"message": "{count, plural, =0 {No se necesita aprobación para las atribuciones} one {# atribución necesita aprobación} other {# atribuciones necesitan aprobación}}"
},
"project.settings.permissions.search-placeholder": {
"message": "Buscar {count} {count, plural, one {proyecto} other {proyectos}}..."
},
"project.settings.permissions.sort.most-files": {
"message": "La mayoría de los archivos"
},
"project.settings.permissions.sort.recently-edited": {
"message": "Recientemente editado"
},
"project.settings.permissions.sort.rejected": {
"message": "Rechazado"
},
"project.settings.permissions.sort.status": {
"message": "Estado"
"message": "Busca {count} {count, plural, one {proyecto externo} other {proyectos externos}}..."
},
"project.settings.title": {
"message": "Configuración"
@@ -4055,48 +3965,6 @@
"settings.account.security.email.title": {
"message": "Correo electrónico"
},
"settings.account.security.passkey.add": {
"message": "Agregar clave de acceso"
},
"settings.account.security.passkey.add-modal.name.description": {
"message": "Asegúrate de elegir algo fácil de recordar, para que puedas identificar esta contraseña más adelante."
},
"settings.account.security.passkey.add-modal.name.label": {
"message": "Nombre"
},
"settings.account.security.passkey.add-modal.name.placeholder": {
"message": "Mi clave de acceso"
},
"settings.account.security.passkey.description": {
"message": "Administra tus claves de acceso registradas o agrega una nueva."
},
"settings.account.security.passkey.modal.added": {
"message": "Agregado {ago}"
},
"settings.account.security.passkey.modal.last-used": {
"message": "Último uso {ago}"
},
"settings.account.security.passkey.modal.loading": {
"message": "Cargando claves de acceso…"
},
"settings.account.security.passkey.modal.never-used": {
"message": "Nunca usado"
},
"settings.account.security.passkey.modal.no-passkeys": {
"message": "No tienes ninguna clave de acceso registrada."
},
"settings.account.security.passkey.remove.description": {
"message": "Esto eliminará de forma permanente la clave de acceso \"{name}\". Ya no podrás iniciar sesión con ella."
},
"settings.account.security.passkey.remove.title": {
"message": "¿Estás seguro de que quieres eliminar esta clave de acceso?"
},
"settings.account.security.passkey.rename-modal.header": {
"message": "Renombrar clave de acceso"
},
"settings.account.security.passkey.title": {
"message": "Administrar claves de acceso"
},
"settings.account.security.password.action.add": {
"message": "Añadir contraseña"
},
@@ -4709,48 +4577,6 @@
"ui.newsletter-button.tooltip": {
"message": "Suscríbete a las noticias de Modrinth"
},
"version.all-versions": {
"message": "Todas las versiones"
},
"version.confirm-delete.description": {
"message": "Esta versión será eliminada permanentemente. Esta acción no se puede deshacer."
},
"version.confirm-delete.proceed": {
"message": "Eliminar versión"
},
"version.confirm-delete.title": {
"message": "¿Estás seguro de que quieres eliminar esta versión?"
},
"version.dependency.view-project": {
"message": "Ver proyecto"
},
"version.dependency.view-version": {
"message": "Ver versión"
},
"version.download.download-dependency": {
"message": "Descargar dependencia"
},
"version.download.no-primary-file": {
"message": "Error: No se encontró el archivo principal"
},
"version.download.optional-resource-pack": {
"message": "Paquete de recursos opcional"
},
"version.download.required-resource-pack": {
"message": "Paquete de recursos requerido"
},
"version.edit.button": {
"message": "Editar"
},
"version.edit.details": {
"message": "Editar detalles"
},
"version.edit.files": {
"message": "Editar archivos"
},
"version.edit.metadata": {
"message": "Editar metadatos"
},
"version.environment.none.description": {
"message": "No se ha especificado el entorno para esta versión."
},
@@ -4762,56 +4588,5 @@
},
"version.environment.unknown.title": {
"message": "Entorno desconocido"
},
"version.package-as-mod.button": {
"message": "Empaquetar como mod"
},
"version.package-as-mod.description": {
"message": "Esto creará una nueva versión con soporte para los cargadores de mods seleccionados. Serás redirigido a la nueva versión y podrás editarla a tu gusto."
},
"version.package-as-mod.header": {
"message": "Empaquetar datapack como mod"
},
"version.package-as-mod.mod-loaders": {
"message": "Cargadores de mods"
},
"version.package-as-mod.mod-loaders.description": {
"message": "Los cargadores de mods para los que te gustaría empaquetar tu paquete de datos"
},
"version.package-as-mod.mod-loaders.placeholder": {
"message": "Elige cargadores de mods..."
},
"version.package-as-mod.submit-button": {
"message": "Paquete de datos"
},
"version.section.content.dev-info": {
"message": "Información del desarrollador"
},
"version.section.content.dev-info.gradle-snippet": {
"message": "build.gradle:"
},
"version.section.content.dev-info.maven-coordinates": {
"message": "Coordenadas de Maven:"
},
"version.section.content.dev-info.maven-description": {
"message": "Los proyectos en Modrinth están automáticamente disponibles a través de un repositorio Maven para su uso con herramientas de construcción de JVM como <gradle-link>Gradle</gradle-link>. Para obtener más información sobre la API de Modrinth Maven, <article-link>haga clic aquí</article-link>."
},
"version.section.content.dev-info.maven-note": {
"message": "Nota: Cuando esté disponible, deberías usar el repositorio maven del creador en su lugar, ya que tendrá información sobre las dependencias transitivas que la API de Modrinth Maven no tiene. También puedes terminar con dependencias duplicadas si usas una mezcla de repositorios Maven de Modrinth y no Modrinth para tus dependencias, porque el identificador de grupo será diferente cuando se sirva a través de la API de Maven de Modrinth."
},
"version.section.content.dev-info.version-id": {
"message": "ID de versión:"
},
"version.supplementary-resources.copy-hash-sha1": {
"message": "Copiar SHA-1"
},
"version.supplementary-resources.copy-hash-sha512": {
"message": "Copiar SHA-512"
},
"version.unknown-embedded-content.description": {
"message": "Esta versión está actualmente retenida y no está listada públicamente. Por favor, proporcione prueba de que tiene permiso para redistribuir ciertos archivos incluidos."
},
"version.unknown-embedded-content.title": {
"message": "Retenido debido a contenido incrustado desconocido"
}
}
+67 -136
View File
@@ -770,9 +770,18 @@
"auth.reset-password.title.long": {
"message": "Restablecer tu contraseña"
},
"auth.sign-in.2fa.description": {
"message": "Por favor introduce el código de autenticación de dos factores para continuar."
},
"auth.sign-in.2fa.label": {
"message": "Introduce el código de autenticación de dos factores"
},
"auth.sign-in.2fa.placeholder": {
"message": "Introducir código..."
},
"auth.sign-in.sign-in-with": {
"message": "Iniciar sesión con"
},
"auth.sign-in.title": {
"message": "Iniciar sesión"
},
@@ -785,6 +794,9 @@
"auth.sign-up.title": {
"message": "Registro"
},
"auth.sign-up.title.sign-up-with": {
"message": "Registrarse con"
},
"auth.verify-email.action.account-settings": {
"message": "Ajustes de cuenta"
},
@@ -1517,6 +1529,9 @@
"dashboard.overview.notifications.loading": {
"message": "Cargando notificaciones..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Puedes editar múltiples proyectos a la vez seleccionándolos abajo."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Proyectos de servidor no soportan edición masiva"
},
@@ -1589,6 +1604,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Por favor revise el entorno del metadata"
},
"dashboard.projects.sort.ascending": {
"message": "Ascendiendo"
},
"dashboard.projects.sort.descending": {
"message": "Descendiendo"
},
"dashboard.projects.sort.option.name": {
"message": "Nombre"
},
"dashboard.projects.sort.option.status": {
"message": "Estado"
},
"dashboard.projects.sort.option.type": {
"message": "Tipo"
},
"dashboard.projects.table.icon": {
"message": "Icono"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -3101,21 +3134,45 @@
"project.details.licensed": {
"message": "Con licencia"
},
"project.download.game-version": {
"message": "Versión del juego: {version}"
},
"project.download.game-version-error": {
"message": "Error: no se han encontrado versiones del juego"
},
"project.download.game-version-tooltip": {
"message": "{title} solo está disponible para {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} no está soportado para la versión {gameVersion} de {platform}"
},
"project.download.install-with-app": {
"message": "Instalar con la aplicación Modrinth"
},
"project.download.no-app": {
"message": "¿No tienes la aplicación Modrinth?"
},
"project.download.no-versions-available": {
"message": "No hay versiones disponibles para {gameVersion} y {platform}."
},
"project.download.platform": {
"message": "Plataforma: {platform}"
},
"project.download.platform-error": {
"message": "Error: plataforma no encontrada"
},
"project.download.platform-tooltip": {
"message": "{title} olo está disponible para {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} no está soportado para {platform} en la {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Buscar versiones del juego..."
},
"project.download.search-game-versions-label": {
"message": "Buscar versiones del juego..."
},
"project.download.select-game-version": {
"message": "Selecciona la versión del juego"
},
@@ -3306,76 +3363,43 @@
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Por favor, proporciona una prueba de que tienes permiso para redistribuir todos los siguientes archivos. Una vez completado, las versiones retenidas se publicarán automáticamente."
"message": "Por favor, proporciona pruebas de que tienes permiso para redistribuir todos estos archivos y cualquier versión retenida se publicará automáticamente."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Por favor, proporciona una prueba de que tiene permiso para redistribuir todos los siguientes archivos antes de enviar su proyecto para su revisión."
"message": "Por favor, proporciona pruebas de que tienes permiso para redistribuir todos estos archivos antes de enviar tu proyecto a revisión."
},
"project.settings.permissions.attention-needed.title": {
"message": "Contenido externo desconocido"
},
"project.settings.permissions.bad-proof.description": {
"message": "El equipo de moderación de Modrinth ha rechazado la información de permisos que proporcionaste para cierto contenido externo. Revisa los elementos rechazados a continuación y proporciona una prueba aceptable o elimina el contenido."
},
"project.settings.permissions.bad-proof.title": {
"message": "Algunas pruebas fueron rechazadas"
},
"project.settings.permissions.collapse-all": {
"message": "Desbloquear todo"
"message": "Incluye contenido desconocido"
},
"project.settings.permissions.completed.description": {
"message": "Todo el contenido externo cuenta con información de permisos y se han proporcionado las atribuciones correspondientes."
"message": "Todo el contenido externo proporciona atribuciones."
},
"project.settings.permissions.completed.title": {
"message": Permisos completados!"
"message": Atribuciones completadas!"
},
"project.settings.permissions.empty-state.description": {
"message": "Ninguna de las versiones de tu proyecto contiene contenido externo, por lo que no necesitas preocuparte por obtener permisos."
"message": "Ninguna de tus versiones presenta contenido externo, por lo que no necesitas preocuparte de obtener permisos."
},
"project.settings.permissions.empty-state.heading": {
"message": "¡Ya estás listo!"
},
"project.settings.permissions.expand-all": {
"message": "Expandir todo"
},
"project.settings.permissions.fail.description": {
"message": "Es posible que no tengas permiso para redistribuir parte del contenido externo en tu proyecto. Para publicar en Modrinth, por favor elimina este contenido o proporciona una prueba de que tienes permiso para usarlo."
"message": "No tienes permiso de redistribuir parte del contenido externo que añadiste. Para poder publicar en Modrinth, quita el contenido infractor."
},
"project.settings.permissions.fail.title": {
"message": "No se puede incluir parte del contenido"
},
"project.settings.permissions.info-banner.description": {
"message": "Si incluyes contenido que no esté alojado en Modrinth, debes informarnos de dónde proviene y verificar que tienes permiso para distribuir los archivos. ¡Consulta <link>nuestro anuncio de este nuevo sistema</link> para obtener más información!"
"message": "Si incluyes contenido que no está alojado en Modrinth, necesitas comunicarnos de dónde viene y verificar que tienes permiso para distribuir los archivos. ¡Mira <link>nuestra guía</link> para saber cómo hacerlo correctamente!"
},
"project.settings.permissions.info-banner.title": {
"message": "Más información sobre los permisos de distribución"
"message": "Aprende cómo funcionan las atribuciones"
},
"project.settings.permissions.learn-more": {
"message": "Saber más"
},
"project.settings.permissions.no-results": {
"message": "No hay archivos externos que coincidan con tu búsqueda."
},
"project.settings.permissions.not-allowed.description": {
"message": "Parte del contenido externo incluido no puede distribuirse en Modrinth porque infringe nuestras Normas de Contenido y debe eliminarse."
},
"project.settings.permissions.pending-approval-count": {
"message": "{count, plural, =0 {No hay atribuciones que necesiten aprobación} one {Una atribución necesita aprobación} other {# atribuciones necesitan aprobación}}"
},
"project.settings.permissions.search-placeholder": {
"message": "Buscar {count} {count, plural, one {proyecto} other {proyectos}}..."
},
"project.settings.permissions.sort.most-files": {
"message": "La mayoría de los archivos"
},
"project.settings.permissions.sort.recently-edited": {
"message": "Recientemente editado"
},
"project.settings.permissions.sort.rejected": {
"message": "Rechazado"
},
"project.settings.permissions.sort.status": {
"message": "Estado"
"message": "Buscar {count} {count, plural, one {proyecto externo} other {proyectos externos}}..."
},
"project.settings.title": {
"message": "Ajustes"
@@ -4547,48 +4571,6 @@
"ui.newsletter-button.tooltip": {
"message": "Suscríbete al boletín de noticias de Modrinth"
},
"version.all-versions": {
"message": "Todas las versiones"
},
"version.confirm-delete.description": {
"message": "Esta versión será permanentemente eliminado. Esta acción no se puede deshacer."
},
"version.confirm-delete.proceed": {
"message": "Eliminar versión"
},
"version.confirm-delete.title": {
"message": "¿Estás seguro de que quieres eliminar esta versión?"
},
"version.dependency.view-project": {
"message": "Ver proyecto"
},
"version.dependency.view-version": {
"message": "Ver versión"
},
"version.download.download-dependency": {
"message": "Descargar dependencia"
},
"version.download.no-primary-file": {
"message": "Error: No se encontró el archivo principal"
},
"version.download.optional-resource-pack": {
"message": "Resourse pack opcional"
},
"version.download.required-resource-pack": {
"message": "Resourse pack requerido"
},
"version.edit.button": {
"message": "Editar"
},
"version.edit.details": {
"message": "Editar detalles"
},
"version.edit.files": {
"message": "Editar archivos"
},
"version.edit.metadata": {
"message": "Editar metadatos"
},
"version.environment.none.description": {
"message": "El entorno para esta versión no ha sido especificado."
},
@@ -4600,56 +4582,5 @@
},
"version.environment.unknown.title": {
"message": "Entorno desconocido"
},
"version.package-as-mod.button": {
"message": "Paquete como mod"
},
"version.package-as-mod.description": {
"message": "Esto creará una nueva versión con soporte a los loader seleccionados. Serás redireccionado a la nueva versión para que puedas editarla a tu gusto."
},
"version.package-as-mod.header": {
"message": "Empaquetar datapack como mod"
},
"version.package-as-mod.mod-loaders": {
"message": "Loaders de mods"
},
"version.package-as-mod.mod-loaders.description": {
"message": "Los loaders para los que te gustaría empaquetar tu datapack."
},
"version.package-as-mod.mod-loaders.placeholder": {
"message": "Elige los loaders..."
},
"version.package-as-mod.submit-button": {
"message": "Empaquetar datapack"
},
"version.section.content.dev-info": {
"message": "Información del desarrollador"
},
"version.section.content.dev-info.gradle-snippet": {
"message": "build.gradle:"
},
"version.section.content.dev-info.maven-coordinates": {
"message": "Coordenadas de Maven:"
},
"version.section.content.dev-info.maven-description": {
"message": "Los proyectos en Modrinth están disponibles automáticamente a través de un repositorio Maven para su uso con herramientas de compilación JVM como <gradle-link>Gradle</gradle-link>. Para obtener más información sobre la API Maven de Modrinth, <article-link>presiona aquí</article-link>."
},
"version.section.content.dev-info.maven-note": {
"message": "Nota: Cuando esté disponible, debería usar el repositorio Maven del creador, ya que contendrá información de dependencia transitiva que la API Maven de Modrinth no tiene. También podría terminar con dependencias duplicadas si usa una combinación de repositorios Maven de Modrinth y de otros proveedores para sus dependencias, porque el identificador de grupo será diferente cuando se sirva a través de la API Maven de Modrinth."
},
"version.section.content.dev-info.version-id": {
"message": "ID de la versión:"
},
"version.supplementary-resources.copy-hash-sha1": {
"message": "Copiar SHA-1"
},
"version.supplementary-resources.copy-hash-sha512": {
"message": "Copiar SHA-512"
},
"version.unknown-embedded-content.description": {
"message": "Esta versión está actualmente retenida y no está listada públicamente. Por favor, proporcione prueba de que tiene permiso para redistribuir ciertos archivos incluidos."
},
"version.unknown-embedded-content.title": {
"message": "Retenido debido a contenido incrustado desconocido"
}
}
+30 -24
View File
@@ -8,24 +8,6 @@
"admin.billing.error.not-found": {
"message": "Käyttäjää ei löydetty"
},
"analytics.action.add": {
"message": "Lisää"
},
"analytics.action.cancel": {
"message": "Peruuta"
},
"analytics.action.refresh": {
"message": "Päivitä"
},
"analytics.action.reset": {
"message": "Palauta"
},
"analytics.breakdown.country": {
"message": "Valtio"
},
"analytics.breakdown.dependent-on": {
"message": "Riippuu kohteesta"
},
"app-marketing.download.description": {
"message": "Työpöytä sovelluksemme on saatavilla kaikilla alustoilla, valitse haluamasi versio."
},
@@ -272,9 +254,18 @@
"auth.reset-password.title.long": {
"message": "Palauta salasanasi"
},
"auth.sign-in.2fa.description": {
"message": "Syötä 2-vaiheisen tunnistautumisen koodi jatkaaksesi."
},
"auth.sign-in.2fa.label": {
"message": "Syötä 2-vaiheisen tunnistautumisen koodi"
},
"auth.sign-in.2fa.placeholder": {
"message": "Anna koodi..."
},
"auth.sign-in.sign-in-with": {
"message": "Kirjaudu sisään käyttäen"
},
"auth.sign-in.title": {
"message": "Kirjaudu sisään"
},
@@ -287,6 +278,9 @@
"auth.sign-up.title": {
"message": "Rekisteröidy"
},
"auth.sign-up.title.sign-up-with": {
"message": "Reskiteröidy käyttäen"
},
"auth.verify-email.action.account-settings": {
"message": "Tilin asetukset"
},
@@ -341,12 +335,6 @@
"collection.delete-modal.title": {
"message": "Oletko varma että haluat poistaa tämän kokoelman?"
},
"collection.description": {
"message": "{description} - Katso kokoelma {name}, jonka on luonut {username}, Modrinthissa"
},
"collection.description.following": {
"message": "Automattisesti luotu kokoelma kaikista projekteista joita seuraat."
},
"collection.editing": {
"message": "Muokataan kokoelmaa"
},
@@ -611,6 +599,24 @@
"dashboard.projects.project.icon-alt": {
"message": "Kuvake projektille {title}"
},
"dashboard.projects.sort.ascending": {
"message": "Nouseva"
},
"dashboard.projects.sort.descending": {
"message": "Laskeva"
},
"dashboard.projects.sort.option.name": {
"message": "Nimi"
},
"dashboard.projects.sort.option.status": {
"message": "Tila"
},
"dashboard.projects.sort.option.type": {
"message": "Tyyppi"
},
"dashboard.projects.table.icon": {
"message": "Kuvake"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -386,9 +386,18 @@
"auth.reset-password.title.long": {
"message": "Baguhin ang iyong password"
},
"auth.sign-in.2fa.description": {
"message": "Pakilagay ng two-factor code upang makapagpatuloy."
},
"auth.sign-in.2fa.label": {
"message": "Ilagay ang two-factor code"
},
"auth.sign-in.2fa.placeholder": {
"message": "Ilagay ang code..."
},
"auth.sign-in.sign-in-with": {
"message": "Mag-sign in gamit ang"
},
"auth.sign-in.title": {
"message": "Mag-sign In"
},
@@ -401,6 +410,9 @@
"auth.sign-up.title": {
"message": "Mag-sign Up"
},
"auth.sign-up.title.sign-up-with": {
"message": "Mag-sign up gamit ang"
},
"auth.verify-email.action.account-settings": {
"message": "Mga setting sa account"
},
@@ -998,6 +1010,18 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Pakisuri sa metadata sa environment"
},
"dashboard.projects.sort.option.name": {
"message": "Pangalan"
},
"dashboard.projects.sort.option.status": {
"message": "Estado"
},
"dashboard.projects.sort.option.type": {
"message": "Uri"
},
"dashboard.projects.table.icon": {
"message": "Ikono"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -2408,21 +2432,45 @@
"project.details.licensed": {
"message": "Lisensiyang"
},
"project.download.game-version": {
"message": "Bersiyon ng laro: {version}"
},
"project.download.game-version-error": {
"message": "Error: walang mahanap na mga bersiyon ng laro"
},
"project.download.game-version-tooltip": {
"message": "Ang {title} ay magagamit lamang sa {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "Hindi masuporta ng {title} ang {gameVersion} para sa {platform}"
},
"project.download.install-with-app": {
"message": "I-install gamit ang Modrinth App"
},
"project.download.no-app": {
"message": "Wala kang Modrinth App?"
},
"project.download.no-versions-available": {
"message": "Walang bersiyong magagamit para sa {gameVersion} at {platform}."
},
"project.download.platform": {
"message": "Plataporma: {platform}"
},
"project.download.platform-error": {
"message": "Error: walang mahanap na platform"
},
"project.download.platform-tooltip": {
"message": "Ang {title} ay magagamit lamang sa {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "Hindi masuport ng {title} ang {platform} para sa {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Maghanap ng mga bersiyon ng laro..."
},
"project.download.search-game-versions-label": {
"message": "Maghanap ng mga bersiyon ng laro..."
},
"project.download.select-game-version": {
"message": "Piliin ang lahat ng bersiyon ng laro"
},
@@ -2537,6 +2585,9 @@
"project.settings.general.url.title": {
"message": "URL"
},
"project.settings.permissions.info-banner.description": {
"message": "Kung magsasama ka ng mga kontentong hindi naka-host sa Modrinth, kailangan niyong ipaalam sa amin sa pinanggalingan nito at matiyak na pinahintulutang mapakalat ang mga file. Tingnan ang <link>aming gabay</link> upang matuto kung papaano ito magawa nang maayos!"
},
"project.settings.title": {
"message": "Mga Setting"
},
+59 -284
View File
@@ -731,72 +731,12 @@
"auth.authorize.authorize-app-name": {
"message": "Autoriser {appName}"
},
"auth.authorize.error.missing-parameters": {
"message": "Paramètres de requête d'authentification obligatoires manquants."
},
"auth.authorize.error.no-redirect-url": {
"message": "Pas d'adresse de redirection trouvée dans la réponse"
},
"auth.authorize.errro-title": {
"message": "Une Erreur est Survenue"
},
"auth.authorize.redirect-url": {
"message": "Vous serez redirigé vers <redirect-url>{url}</redirect-url>"
},
"auth.continue-with-provider": {
"message": "Continuer avec {provider}"
},
"auth.create-account.age-requirement.warning-title": {
"message": "Conditions d'âge"
},
"auth.create-account.complete-sign-up": {
"message": "Terminer l'inscription"
},
"auth.create-account.date-of-birth.invalid.text": {
"message": "Veuillez entrer une année de naissance valide. L'année ne peut pas être 0000."
},
"auth.create-account.date-of-birth.invalid.title": {
"message": "Date de naissance invalide"
},
"auth.create-account.date-of-birth.label": {
"message": "Date de naissance"
},
"auth.create-account.date-of-birth.placeholder": {
"message": "Choisissez votre date de naissance"
},
"auth.create-account.date-of-birth.required.text": {
"message": "Veuillez entrer votre date de naissance avant de continuer."
},
"auth.create-account.date-of-birth.required.title": {
"message": "Date de naissance requise"
},
"auth.create-account.date-of-birth.under13-helper": {
"message": "Vous ne rencontrez pas les conditions d'âge requises pour créer un compte sur Modrinth."
},
"auth.create-account.info-panel.source-code-link": {
"message": "Code source pertinent"
},
"auth.create-account.info-panel.text": {
"message": "Nous ne conservons pas votre date de naissance. Nous l'utilisons uniquement lors de l'inscription pour vérifier la conformité avec la loi COPPA."
},
"auth.create-account.page-title": {
"message": "Créer un Compte"
},
"auth.create-account.security-check.label": {
"message": "Vérification de sécurité"
},
"auth.create-account.subscribe.label": {
"message": "Me tenir au courant des trucs cools sur lesquels Modrinth travaille par e-mail"
},
"auth.create-account.title": {
"message": "Créer un Compte"
},
"auth.create-account.username.label": {
"message": "Pseudo"
},
"auth.create-account.username.placeholder": {
"message": "Entrez un pseudo"
},
"auth.reset-password.method-choice.action": {
"message": "Envoyez un e-mail de récupération"
},
@@ -831,53 +771,23 @@
"message": "Réinitialiser votre mot de passe"
},
"auth.sign-in.2fa.description": {
"message": "Entrez le code à 6 chiffres de votre application d'authentification, ou un de vos codes de secours."
"message": "Veuillez entrer un code à double facteur pour continuer."
},
"auth.sign-in.2fa.label": {
"message": "Authentification à deux facteurs"
"message": "Saisir le code à double facteur"
},
"auth.sign-in.2fa.placeholder": {
"message": "Saisir le code..."
},
"auth.sign-in.continue-with-email": {
"message": "Continuer avec un E-mail"
},
"auth.sign-in.continue-with-passkey": {
"message": "Continuer avec une clé d'accès"
},
"auth.sign-in.create-account": {
"message": "S'inscrire"
},
"auth.sign-in.forgot-password": {
"message": "Mot de passe oublié"
},
"auth.sign-in.last-sign-in": {
"message": "Dernière utilisation"
},
"auth.sign-in.no-account": {
"message": "Vous n'avez pas de compte ?"
},
"auth.sign-in.sign-in-with": {
"message": "Se connecter à Modrinth"
"message": "Se connecter avec"
},
"auth.sign-in.title": {
"message": "Se connecter"
},
"auth.sign-up.age-requirement.warning-title": {
"message": "Conditions d'âge"
},
"auth.sign-up.continue-with-email": {
"message": "Continuer avec un E-mail"
},
"auth.sign-up.legal-dislaimer": {
"message": "En créant un compte, vous acceptez les <terms-link>Conditions d'utilisation</terms-link> et la <privacy-policy-link>Politique de confidentialité</privacy-policy-link> de Modrinth."
},
"auth.sign-up.show-fewer-options": {
"message": "Voir moins d'options"
},
"auth.sign-up.show-other-options": {
"message": "Voir plus d'options"
},
"auth.sign-up.sign-in-option.title": {
"message": "Vous avez déjà un compte ?"
},
@@ -885,14 +795,11 @@
"message": "S'inscrire"
},
"auth.sign-up.title.sign-up-with": {
"message": "Créer un Compte"
"message": "S'inscrire avec"
},
"auth.verify-email.action.account-settings": {
"message": "Paramètres du compte"
},
"auth.verify-email.action.discover-mods": {
"message": "Découvrez des mods"
},
"auth.verify-email.already-verified.description": {
"message": "Votre adresse e-mail est déjà vérifiée !"
},
@@ -911,15 +818,6 @@
"auth.verify-email.failed-verification.title": {
"message": "La vérification de l'adresse e-mail a échouée"
},
"auth.verify-email.notification.email-sent.description": {
"message": "Un e-mail avec un lien pour vérifier votre compte a été envoyé à {email}."
},
"auth.verify-email.notification.email-sent.title": {
"message": "E-mail envoyé"
},
"auth.verify-email.notification.error-occurred.title": {
"message": "Une erreur est survenue"
},
"auth.verify-email.post-verification.description": {
"message": "Votre adresse e-mail a été vérifiée avec succès !"
},
@@ -1634,6 +1532,9 @@
"dashboard.overview.notifications.loading": {
"message": "Chargement des notifications..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Vous pouvez modifier plusieurs projets en même temps en les sélectionnant ci-dessous."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Les projets du serveur ne prennent pas en charge l'édition en masse"
},
@@ -1709,6 +1610,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Merci de revoir les métadonnées environnementales"
},
"dashboard.projects.sort.ascending": {
"message": "Ascendant"
},
"dashboard.projects.sort.descending": {
"message": "Descendant"
},
"dashboard.projects.sort.option.name": {
"message": "Nom"
},
"dashboard.projects.sort.option.status": {
"message": "Statut"
},
"dashboard.projects.sort.option.type": {
"message": "Type"
},
"dashboard.projects.table.icon": {
"message": "Icône"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -3221,21 +3140,45 @@
"project.details.licensed": {
"message": "License"
},
"project.download.game-version": {
"message": "Version du jeu : {version}"
},
"project.download.game-version-error": {
"message": "Erreur : pas de versions du jeu trouvées"
},
"project.download.game-version-tooltip": {
"message": "{title} n'est disponible que pour {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} ne supporte pas {gameVersion} pour {platform}"
},
"project.download.install-with-app": {
"message": "Installer avec Modrinth App"
},
"project.download.no-app": {
"message": "Vous n'avez pas Modrinth App ?"
},
"project.download.no-versions-available": {
"message": "Aucune version disponible pour {gameVersion} et {platform}."
},
"project.download.platform": {
"message": "Plateforme : {platform}"
},
"project.download.platform-error": {
"message": "Erreur : pas de plateformes trouvées"
},
"project.download.platform-tooltip": {
"message": "{title} est que disponible pour {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} ne supporte pas {platform} pour {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Recherchez une version du jeu..."
},
"project.download.search-game-versions-label": {
"message": "Recherchez une version du jeu..."
},
"project.download.select-game-version": {
"message": "Sélectionnez la version du jeu"
},
@@ -3426,76 +3369,43 @@
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Veuillez fournir la preuve que vous êtes autorisé à redistribuer tous les fichiers suivants. Une fois cette étape terminée, les versions actuellement retenues seront automatiquement publiées."
"message": "Veuillez prouver que vous avez la permission de redistribuer tous les fichiers suivants et que toutes les versions retenues seront automatiquement publiées."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Veuillez fournir la preuve que vous êtes autorisé à redistribuer tous les fichiers suivants avant de soumettre votre projet pour examen."
"message": "Veuillez fournir la preuve que vous avez l'autorisation de redistribuer tous les fichiers suivants avant de pouvoir soumettre votre projet pour évaluation."
},
"project.settings.permissions.attention-needed.title": {
"message": "Contenu externe inconnu"
},
"project.settings.permissions.bad-proof.description": {
"message": "L'équipe de modération de Modrinth a rejeté les informations d'autorisation que vous avez fournies pour certains contenus externes. Veuillez examiner les éléments rejetés ci-dessous et fournir une preuve acceptable lub supprimer le contenu."
},
"project.settings.permissions.bad-proof.title": {
"message": "Certaines preuves ont été rejetées"
},
"project.settings.permissions.collapse-all": {
"message": "Tout réduire"
"message": "Contenu intégré inconnu"
},
"project.settings.permissions.completed.description": {
"message": "Toutes les informations d'autorisation et les attributions ont été fournies pour l'ensemble du contenu externe."
"message": "Tout le contenu externe est accompagné d'une attribution."
},
"project.settings.permissions.completed.title": {
"message": "Autorisations terminées !"
"message": "Attributions terminées !"
},
"project.settings.permissions.empty-state.description": {
"message": "Aucune des versions de votre projet ne contient de contenu externe, vous n'avez donc pas à vous soucier d'obtenir des autorisations."
"message": "Aucune de vos versions ne contient de contenu externe, vous n'avez donc pas à vous soucier d'obtenir d'autorisations."
},
"project.settings.permissions.empty-state.heading": {
"message": "Vous êtes prêt !"
},
"project.settings.permissions.expand-all": {
"message": "Tout développer"
},
"project.settings.permissions.fail.description": {
"message": "Il se peut que vous n'ayez pas l'autorisation de redistribuer une partie du contenu externe de votre projet. Pour pouvoir publier na Modrinth, veuillez supprimer ce contenu ou fournir la preuve que vous avez l'autorisation de l'utiliser."
"message": "Vous n'êtes pas autorisé à rediffuser certains des contenus externes que vous avez ajoutés. Pour pouvoir publier sur Modrinth, veuillez supprimer les contenus litigieux."
},
"project.settings.permissions.fail.title": {
"message": "Certains contenus ne peuvent pas être inclus"
},
"project.settings.permissions.info-banner.description": {
"message": "Si vous incluez du contenu qui n'est pas hébergé sur Modrinth, vous devez nous indiquer sa provenance et vérifier que vous avez l'autorisation de distribuer te pliki. Découvrez <link>notre annonce de ce nouveau système</link> pour en savoir plus !"
"message": "Si vous incluez du contenu qui nest pas sur Modrinth, vous devez nous faire savoir doù il vient et vérifier que vous avez la permission de distribuer les fichiers. Jettez un coup d’œil à <link>notre guide</link> pour apprendre comment faire cela proprement !"
},
"project.settings.permissions.info-banner.title": {
"message": "En savoir plus sur les autorisations de distribution"
"message": "Découvrez comment fonctionnent les attributions"
},
"project.settings.permissions.learn-more": {
"message": "En savoir plus"
},
"project.settings.permissions.no-results": {
"message": "Aucun fichier externe ne correspond à votre recherche."
},
"project.settings.permissions.not-allowed.description": {
"message": "Une partie du contenu externe inclus ne peut pas être distribuée sur Modrinth car elle enfreint nos règles relatives au contenu et doit être supprimée."
},
"project.settings.permissions.pending-approval-count": {
"message": "{count, plural, =0 {Aucune attribution n'a besoin d'approbation} one {# attribution a besoin d'approbation} other {# attributions ont besoin d'approbation}}"
},
"project.settings.permissions.search-placeholder": {
"message": "Rechercher {count} {count, plural, one {projet} other {projets}}..."
},
"project.settings.permissions.sort.most-files": {
"message": "La plupart des fichiers"
},
"project.settings.permissions.sort.recently-edited": {
"message": "Modifié récemment"
},
"project.settings.permissions.sort.rejected": {
"message": "Rejeté"
},
"project.settings.permissions.sort.status": {
"message": "Statut"
"message": "Rechercher {count} {count, plural, one {projet externe} other {projets externes}}..."
},
"project.settings.title": {
"message": "Paramètres"
@@ -4055,48 +3965,6 @@
"settings.account.security.email.title": {
"message": "E-mail"
},
"settings.account.security.passkey.add": {
"message": "Ajouter une clé d'accès"
},
"settings.account.security.passkey.add-modal.name.description": {
"message": "Veillez à choisir un nom facile à retenir, afin de pouvoir identifier tę klucz d'accès plus tard."
},
"settings.account.security.passkey.add-modal.name.label": {
"message": "Nom"
},
"settings.account.security.passkey.add-modal.name.placeholder": {
"message": "Ma clé d'accès"
},
"settings.account.security.passkey.description": {
"message": "Gérez vos clés d'accès enregistrées ou ajoutez-en une nouvelle."
},
"settings.account.security.passkey.modal.added": {
"message": "Ajouté il y a {ago}"
},
"settings.account.security.passkey.modal.last-used": {
"message": "Dernière utilisation il y a {ago}"
},
"settings.account.security.passkey.modal.loading": {
"message": "Chargement des clés d'accès…"
},
"settings.account.security.passkey.modal.never-used": {
"message": "Jamais utilisée"
},
"settings.account.security.passkey.modal.no-passkeys": {
"message": "Vous n'avez aucune clé d'accès enregistrée."
},
"settings.account.security.passkey.remove.description": {
"message": "Cela supprimera définitivement la clé d'accès \"{name}\". Vous ne pourrez plus l'utiliser pour vous connecter."
},
"settings.account.security.passkey.remove.title": {
"message": "Êtes-vous sûr de vouloir supprimer cette clé d'accès ?"
},
"settings.account.security.passkey.rename-modal.header": {
"message": "Renommer la clé d'accès"
},
"settings.account.security.passkey.title": {
"message": "Gérer les clés d'accès"
},
"settings.account.security.password.action.add": {
"message": "Ajouter un mot de passe"
},
@@ -4709,48 +4577,6 @@
"ui.newsletter-button.tooltip": {
"message": "S'abonner au journal Modrinth"
},
"version.all-versions": {
"message": "Toutes les versions"
},
"version.confirm-delete.description": {
"message": "Cette version sera définitivement supprimée. Cette action est irréversible."
},
"version.confirm-delete.proceed": {
"message": "Supprimer la version"
},
"version.confirm-delete.title": {
"message": "Êtes-vous sûr de vouloir supprimer cette version ?"
},
"version.dependency.view-project": {
"message": "Voir le projet"
},
"version.dependency.view-version": {
"message": "Voir la version"
},
"version.download.download-dependency": {
"message": "Télécharger la dépendance"
},
"version.download.no-primary-file": {
"message": "Erreur : aucun fichier principal trouvé"
},
"version.download.optional-resource-pack": {
"message": "Pack de ressources facultatif"
},
"version.download.required-resource-pack": {
"message": "Pack de ressources requis"
},
"version.edit.button": {
"message": "Modifier"
},
"version.edit.details": {
"message": "Modifier les détails"
},
"version.edit.files": {
"message": "Modifier los pliki"
},
"version.edit.metadata": {
"message": "Modifier les métadonnées"
},
"version.environment.none.description": {
"message": "L'environnement pour cette version n'a pas été spécifié."
},
@@ -4762,56 +4588,5 @@
},
"version.environment.unknown.title": {
"message": "Environnement Inconnue"
},
"version.package-as-mod.button": {
"message": "Packager comme mod"
},
"version.package-as-mod.description": {
"message": "Cela créera une nouvelle version prenant en charge les chargeurs de mods sélectionnés. Vous serez redirigé vers la nouvelle version et pourrez la modifier à votre guise."
},
"version.package-as-mod.header": {
"message": "Empaqueter le pack de données en mod"
},
"version.package-as-mod.mod-loaders": {
"message": "Chargeurs de mods"
},
"version.package-as-mod.mod-loaders.description": {
"message": "Les chargeurs de mods pour lesquels vous souhaitez packager votre data pack."
},
"version.package-as-mod.mod-loaders.placeholder": {
"message": "Choisir les chargeurs de mods..."
},
"version.package-as-mod.submit-button": {
"message": "Packager le data pack"
},
"version.section.content.dev-info": {
"message": "Informations de développement"
},
"version.section.content.dev-info.gradle-snippet": {
"message": "build.gradle :"
},
"version.section.content.dev-info.maven-coordinates": {
"message": "Coordonnées Maven :"
},
"version.section.content.dev-info.maven-description": {
"message": "Les projets sur Modrinth sont automatiquement disponibles via un dépôt Maven pour une utilisation avec des outils de build JVM tels que <gradle-link>Gradle</gradle-link>. Pour en savoir plus sur l'API Maven de Modrinth, <article-link>cliquez ici</article-link>."
},
"version.section.content.dev-info.maven-note": {
"message": "Remarque : Lorsque cela est possible, il est préférable d'utiliser le dépôt Maven du créateur, car il contient des informations sur les dépendances transitives que l'API Maven de Modrinth ne fournit pas. Vous risquez également de vous retrouver avec des dépendances en double si vous combinez des dépôts Maven Modrinth et non-Modrinth pour vos dépendances, car l'identifiant de groupe (group ID) différera lorsqu'il est fourni via l'API Maven de Modrinth."
},
"version.section.content.dev-info.version-id": {
"message": "ID de version :"
},
"version.supplementary-resources.copy-hash-sha1": {
"message": "Copier SHA-1"
},
"version.supplementary-resources.copy-hash-sha512": {
"message": "Copier SHA-512"
},
"version.unknown-embedded-content.description": {
"message": "Cette version est actuellement retenue et ne figure pas dans la liste publique. Veuillez fournir la preuve que vous êtes autorisé à redistribuer certains des fichiers inclus."
},
"version.unknown-embedded-content.title": {
"message": "Masqué en raison d'un contenu intégré inconnu"
}
}
@@ -254,9 +254,18 @@
"auth.reset-password.title.long": {
"message": "הפס סיסמתכם"
},
"auth.sign-in.2fa.description": {
"message": "כתבו את הקוד אימות דו-שלבי שלכם כדי להמשיך."
},
"auth.sign-in.2fa.label": {
"message": "כתבו את האימות דו-שלבי"
},
"auth.sign-in.2fa.placeholder": {
"message": "כתבו קוד..."
},
"auth.sign-in.sign-in-with": {
"message": "הכנס אם"
},
"auth.sign-in.title": {
"message": "הכנס"
},
@@ -269,6 +278,9 @@
"auth.sign-up.title": {
"message": "הרשם"
},
"auth.sign-up.title.sign-up-with": {
"message": "הרשם אם"
},
"auth.verify-email.action.account-settings": {
"message": "הגדרות חשבון"
},
@@ -2027,21 +2039,45 @@
"project.details.licensed": {
"message": "ברישיון"
},
"project.download.game-version": {
"message": "גרסת המשחק: {version}"
},
"project.download.game-version-error": {
"message": "שגיאה: לא נמצאו גרסאות משחק"
},
"project.download.game-version-tooltip": {
"message": "{title} זמין עבור {version} בלבד"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} אינו תומך ב-{gameVersion} עבור {platform}"
},
"project.download.install-with-app": {
"message": "התקן באמצעות Modrinth App"
},
"project.download.no-app": {
"message": "אין לכם את אפליקציית Modrinth?"
},
"project.download.no-versions-available": {
"message": "אין גרסאות זמינות עבור {gameVersion} ו-{platform}."
},
"project.download.platform": {
"message": "פלטפורמה: {platform}"
},
"project.download.platform-error": {
"message": "שגיאה: לא נמצאו פלטפורמות"
},
"project.download.platform-tooltip": {
"message": "{title} זמין עבור {platform} בלבד"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} אינו תומך ב-{platform} עבור {gameVersion}"
},
"project.download.search-game-versions": {
"message": "חיפוש גרסאות משחק..."
},
"project.download.search-game-versions-label": {
"message": "חיפוש גרסאות משחק..."
},
"project.download.select-game-version": {
"message": "בחירת גרסת משחק"
},
+63 -6
View File
@@ -38,9 +38,6 @@
"analytics.breakdown.loader": {
"message": "Betöltő"
},
"analytics.breakdown.members": {
"message": "Tag"
},
"analytics.breakdown.monetization": {
"message": "Bevételszerzés"
},
@@ -224,9 +221,6 @@
"analytics.filter.search.download-sources": {
"message": "Letöltési források keresése..."
},
"analytics.filter.search.members": {
"message": "Tagok keresése... "
},
"analytics.filter.search.project-versions": {
"message": "Projektverziók keresése..."
},
@@ -686,9 +680,18 @@
"auth.reset-password.title.long": {
"message": "Jelszavad visszaállítása"
},
"auth.sign-in.2fa.description": {
"message": "Kérlek írd be a kétlépcsős hitelesítés kódját a folytatáshoz."
},
"auth.sign-in.2fa.label": {
"message": "Írd be a kétlépcsős hitelesítés kódját"
},
"auth.sign-in.2fa.placeholder": {
"message": "Kód beírása..."
},
"auth.sign-in.sign-in-with": {
"message": "Bejelentkezés ezzel"
},
"auth.sign-in.title": {
"message": "Bejelentkezés"
},
@@ -701,6 +704,9 @@
"auth.sign-up.title": {
"message": "Regisztráció"
},
"auth.sign-up.title.sign-up-with": {
"message": "Regisztráció ezzel"
},
"auth.verify-email.action.account-settings": {
"message": "Fiókbeállítások"
},
@@ -1382,6 +1388,9 @@
"dashboard.overview.notifications.loading": {
"message": "Értesítések betöltése..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Több projektet is szerkeszthetsz egyszerre, ha kiválasztod őket alább."
},
"dashboard.projects.empty": {
"message": "Még nincsenek projektjeid. Kattints a fenti zöld gombra a kezdéshez."
},
@@ -1442,6 +1451,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Kérlek ellenőrizd a környezeti metaadatokat"
},
"dashboard.projects.sort.ascending": {
"message": "Növekvő"
},
"dashboard.projects.sort.descending": {
"message": "Csökkenő"
},
"dashboard.projects.sort.option.name": {
"message": "Név"
},
"dashboard.projects.sort.option.status": {
"message": "Állapot"
},
"dashboard.projects.sort.option.type": {
"message": "Típus"
},
"dashboard.projects.table.icon": {
"message": "Ikon"
},
"dashboard.projects.table.id": {
"message": "Azonosító"
},
@@ -2918,21 +2945,45 @@
"project.details.licensed": {
"message": "Licensz:"
},
"project.download.game-version": {
"message": "Játékverzió: {version}"
},
"project.download.game-version-error": {
"message": "Hiba: nem található játékverzió"
},
"project.download.game-version-tooltip": {
"message": "{title} csak erre a verzióra érthető el: {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "A(z) {title} nem támogatja ezt a verziót: {platform} {gameVersion}"
},
"project.download.install-with-app": {
"message": "Letöltés a Modrinth Appal"
},
"project.download.no-app": {
"message": "Nincs meg a Modrinth App? "
},
"project.download.no-versions-available": {
"message": "Nem érhető el verzió ehhez: {platform} {gameVersion}"
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Hiba: nem található platform"
},
"project.download.platform-tooltip": {
"message": "A(z) {title} csak erre érthető el: {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "A(z) {title} nem támogatja ezt a verziót: {platform} {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Játékverziók keresése..."
},
"project.download.search-game-versions-label": {
"message": "Játékverziók keresése..."
},
"project.download.select-game-version": {
"message": "Játékverzió kiválasztása"
},
@@ -3077,12 +3128,18 @@
"project.settings.general.url.title": {
"message": "Link"
},
"project.settings.permissions.empty-state.description": {
"message": "Egyik verziód sem tartalmaz külső tartalmat, így nem kell aggódnod a jogosultságok beszerzése miatt."
},
"project.settings.permissions.empty-state.heading": {
"message": "Kész is van!"
},
"project.settings.permissions.learn-more": {
"message": "További információ"
},
"project.settings.permissions.search-placeholder": {
"message": "Keresés {count} külső projekt között..."
},
"project.settings.title": {
"message": "Beállítások"
},
@@ -473,9 +473,18 @@
"auth.reset-password.title.long": {
"message": "Atur ulang kata sandi Anda"
},
"auth.sign-in.2fa.description": {
"message": "Mohon masukkan kode autentikasi dwifaktor untuk melanjutkan."
},
"auth.sign-in.2fa.label": {
"message": "Masukkan kode dwifaktor"
},
"auth.sign-in.2fa.placeholder": {
"message": "Masukkan kode..."
},
"auth.sign-in.sign-in-with": {
"message": "Masuk dengan"
},
"auth.sign-in.title": {
"message": "Masuk"
},
@@ -488,6 +497,9 @@
"auth.sign-up.title": {
"message": "Daftar"
},
"auth.sign-up.title.sign-up-with": {
"message": "Daftar dengan"
},
"auth.verify-email.action.account-settings": {
"message": "Pengaturan akun"
},
@@ -2423,21 +2435,45 @@
"project.details.licensed": {
"message": "Berlisensi"
},
"project.download.game-version": {
"message": "Versi permainan: {version}"
},
"project.download.game-version-error": {
"message": "Kesalahan: tidak ada versi permainan yang ditemukan"
},
"project.download.game-version-tooltip": {
"message": "{title} hanya tersedia untuk {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} tidak mendukung {gameVersion} untuk {platform}"
},
"project.download.install-with-app": {
"message": "Pasang dengan Modrinth App"
},
"project.download.no-app": {
"message": "Tidak memiliki Modrinth App?"
},
"project.download.no-versions-available": {
"message": "Tidak ada versi tersedia untuk {gameVersion} dan {platform}."
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Kesalahan: tidak ada platform yang ditemukan"
},
"project.download.platform-tooltip": {
"message": "{title} hanya tersedia untuk {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} tidak mendukung {platform} untuk {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Cari versi permainan..."
},
"project.download.search-game-versions-label": {
"message": "Cari versi permainan..."
},
"project.download.select-game-version": {
"message": "Pilih versi permainan"
},
+59 -284
View File
@@ -725,72 +725,12 @@
"auth.authorize.authorize-app-name": {
"message": "Autorizza {appName}"
},
"auth.authorize.error.missing-parameters": {
"message": "Parametri di query OAuth mancanti."
},
"auth.authorize.error.no-redirect-url": {
"message": "Nessun link di reindirizzamento trovato nella risposta"
},
"auth.authorize.errro-title": {
"message": "Si è verificato un errore"
},
"auth.authorize.redirect-url": {
"message": "Sarai reindirizzato a <redirect-url>{url}</redirect-url>"
},
"auth.continue-with-provider": {
"message": "Continua con {provider}"
},
"auth.create-account.age-requirement.warning-title": {
"message": "Età richiesta"
},
"auth.create-account.complete-sign-up": {
"message": "Completa la registrazione"
},
"auth.create-account.date-of-birth.invalid.text": {
"message": "Inserisci una data di nascita valida. L'anno non può essere 0000."
},
"auth.create-account.date-of-birth.invalid.title": {
"message": "Data di nascita non valida"
},
"auth.create-account.date-of-birth.label": {
"message": "Data di nascita"
},
"auth.create-account.date-of-birth.placeholder": {
"message": "Seleziona la tua data di nascita"
},
"auth.create-account.date-of-birth.required.text": {
"message": "Inserisci la tua data di nascita prima di continuare."
},
"auth.create-account.date-of-birth.required.title": {
"message": "Data di nascita richiesta"
},
"auth.create-account.date-of-birth.under13-helper": {
"message": "Non soddisfi i requisiti di età necessari per creare un account su Modrinth."
},
"auth.create-account.info-panel.source-code-link": {
"message": "Codice sorgente rilevante"
},
"auth.create-account.info-panel.text": {
"message": "Non memorizziamo la tua data di nascita. La utilizziamo solo durante la registrazione per verificare la conformità alla normativa COPPA."
},
"auth.create-account.page-title": {
"message": "Crea account"
},
"auth.create-account.security-check.label": {
"message": "Controllo di sicurezza"
},
"auth.create-account.subscribe.label": {
"message": "Tienimi aggiornato sulle fantastiche cose a cui Modrinth sta lavorando tramite email"
},
"auth.create-account.title": {
"message": "Crea un account"
},
"auth.create-account.username.label": {
"message": "Nome utente"
},
"auth.create-account.username.placeholder": {
"message": "Inserisci il nome utente"
},
"auth.reset-password.method-choice.action": {
"message": "Invia email di recupero"
},
@@ -825,53 +765,23 @@
"message": "Reimposta la password"
},
"auth.sign-in.2fa.description": {
"message": "Inserisci il codice a 6 cifre dalla tua app di autenticazione oppure uno dei tuoi codici di backup."
"message": "Inserisci il codice 2FA per continuare."
},
"auth.sign-in.2fa.label": {
"message": "Autenticazione a due fattori"
"message": "Inserisci un codice 2FA"
},
"auth.sign-in.2fa.placeholder": {
"message": "Inserisci il codice..."
},
"auth.sign-in.continue-with-email": {
"message": "Continua con l'email"
},
"auth.sign-in.continue-with-passkey": {
"message": "Continua con passkey"
},
"auth.sign-in.create-account": {
"message": "Registrati"
},
"auth.sign-in.forgot-password": {
"message": "Password dimenticata"
},
"auth.sign-in.last-sign-in": {
"message": "Ultimo utilizzo"
},
"auth.sign-in.no-account": {
"message": "Non hai un account?"
},
"auth.sign-in.sign-in-with": {
"message": "Accedi a Modrinth"
"message": "Accedi con"
},
"auth.sign-in.title": {
"message": "Accedi"
},
"auth.sign-up.age-requirement.warning-title": {
"message": "Età richiesta"
},
"auth.sign-up.continue-with-email": {
"message": "Continua con l'email"
},
"auth.sign-up.legal-dislaimer": {
"message": "Creando un''account, accetti i <terms-link>termini d''uso</terms-link> e l''<privacy-policy-link>informativa sulla privacy</privacy-policy-link> di Modrinth."
},
"auth.sign-up.show-fewer-options": {
"message": "Mostra meno opzioni"
},
"auth.sign-up.show-other-options": {
"message": "Mostra altre opzioni"
},
"auth.sign-up.sign-in-option.title": {
"message": "Hai già un account?"
},
@@ -879,14 +789,11 @@
"message": "Registrati"
},
"auth.sign-up.title.sign-up-with": {
"message": "Crea un account"
"message": "Accedi con"
},
"auth.verify-email.action.account-settings": {
"message": "Impostazioni account"
},
"auth.verify-email.action.discover-mods": {
"message": "Esplora mod"
},
"auth.verify-email.already-verified.description": {
"message": "La tua email è già stata verificata!"
},
@@ -905,15 +812,6 @@
"auth.verify-email.failed-verification.title": {
"message": "Errore nella verifica dell'email"
},
"auth.verify-email.notification.email-sent.description": {
"message": "È stata inviata un'email con il link per verificare il tuo account a {email}."
},
"auth.verify-email.notification.email-sent.title": {
"message": "Email inviata"
},
"auth.verify-email.notification.error-occurred.title": {
"message": "Si è verificato un errore"
},
"auth.verify-email.post-verification.description": {
"message": "Il tuo indirizzo email è stato verificato con successo!"
},
@@ -1628,6 +1526,9 @@
"dashboard.overview.notifications.loading": {
"message": "Caricando le notifiche..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Puoi modificare più progetti contemporaneamente selezionandoli qui sotto."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Non puoi modificare più server contemporaneamente"
},
@@ -1700,6 +1601,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Si prega di rivedere i metadati d'ambiente"
},
"dashboard.projects.sort.ascending": {
"message": "Crescente"
},
"dashboard.projects.sort.descending": {
"message": "Decrescente"
},
"dashboard.projects.sort.option.name": {
"message": "Nome"
},
"dashboard.projects.sort.option.status": {
"message": "Stato"
},
"dashboard.projects.sort.option.type": {
"message": "Tipo"
},
"dashboard.projects.table.icon": {
"message": "Icona"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -2133,7 +2052,7 @@
"message": "Avvia il tuo server"
},
"hosting-marketing.hero.host-with-modrinth": {
"message": "Scegli Modrinth Hosting per il tuo nuovo server"
"message": "Affida il tuo prossimo server a Modrinth Hosting"
},
"hosting-marketing.hero.hosting-description": {
"message": "Modrinth Hosting è il modo più facile per gestire il tuo server personale per Minecraft: Java Edition. Installa e gioca alle tue mod e pacchetti preferiti, tutto tramite la piattaforma Modrinth."
@@ -3212,21 +3131,45 @@
"project.details.licensed": {
"message": "Licenza"
},
"project.download.game-version": {
"message": "Versione del gioco: {version}"
},
"project.download.game-version-error": {
"message": "Errore: nessuna versione del gioco trovata"
},
"project.download.game-version-tooltip": {
"message": "{title} è disponibile solo per {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} non supporta {platform} per {gameVersion}"
},
"project.download.install-with-app": {
"message": "Installa con Modrinth App"
},
"project.download.no-app": {
"message": "Non hai Modrinth App?"
},
"project.download.no-versions-available": {
"message": "Nessuna versione disponibile per {gameVersion} e {platform}."
},
"project.download.platform": {
"message": "Piattaforma: {platform}"
},
"project.download.platform-error": {
"message": "Errore: nessuna piattaforma trovata"
},
"project.download.platform-tooltip": {
"message": "{title} è disponibile solo per {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} non supporta {platform} per {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Cerca versioni del gioco..."
},
"project.download.search-game-versions-label": {
"message": "Cerca tra le versioni del gioco..."
},
"project.download.select-game-version": {
"message": "Seleziona la versione del gioco"
},
@@ -3417,76 +3360,43 @@
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Dimostra di avere il permesso di ridistribuire tutti i file seguenti. Una volta fatto, le versioni sospese verranno pubblicate automaticamente."
"message": "Dimostra di avere il permesso di ridistribuire tutti i seguenti file, e le versioni sospese verranno pubblicate automaticamente."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Dimostra di avere il permesso di ridistribuire tutti i file seguenti prima di presentare il progetto alla revisione."
"message": "Dimostra di avere il permesso di ridistribuire tutti i seguenti file prima di presentare il tuo progetto alla revisione."
},
"project.settings.permissions.attention-needed.title": {
"message": "Contenuti di origine sconosciuta"
},
"project.settings.permissions.bad-proof.description": {
"message": "Il team di moderazione di Modrinth ha rifiutato i permessi che hai dimostrato per alcuni contenuti esterni. Ricontrolla gli elementi rifiutati riportati di seguito e fornisci delle prove valide oppure rimuovi i contenuti."
},
"project.settings.permissions.bad-proof.title": {
"message": "Alcune prove sono state rifiutate"
},
"project.settings.permissions.collapse-all": {
"message": "Comprimi tutto"
},
"project.settings.permissions.completed.description": {
"message": "Tutti i contenuti esterni hanno ricevuto i permessi e tutte le attribuzioni sono state fornite."
"message": "Sono state fornite attribuzioni per tutti i contenuti esterni."
},
"project.settings.permissions.completed.title": {
"message": "Permessi completi!"
"message": "Attribuzioni complete!"
},
"project.settings.permissions.empty-state.description": {
"message": "Nessuna versione del progetto include contenuti esterni, quindi non è necessario dimostrare alcun permesso."
"message": "Nessuna versione include contenuti esterni, quindi non è necessario dimostrare alcun permesso."
},
"project.settings.permissions.empty-state.heading": {
"message": "Tutto a posto!"
},
"project.settings.permissions.expand-all": {
"message": "Espandi tutto"
},
"project.settings.permissions.fail.description": {
"message": "Potresti non avere il permesso di ridistribuire alcuni dei contenuti esterni contenuti nel progetto. Per pubblicare su Modrinth, rimuovili o dimostra di avere il permesso di usarli."
"message": "Non hai il permesso di ridistribuire alcuni dei contenuti esterni che hai incluso. Per pubblicare su Modrinth, rimuovi i contenuti illeciti."
},
"project.settings.permissions.fail.title": {
"message": "Alcuni contenuti non possono essere pubblicati"
},
"project.settings.permissions.info-banner.description": {
"message": "Se includi dei contenuti esterni a Modrinth, devi dichiarare la loro origine e dimostrare di avere il permesso di ridistribuirli. Controlla <link>il nostro annuncio su questo nuovo sistema</link> per scoprire di più!"
"message": "Se includi dei contenuti esterni a Modrinth, devi dichiarare la loro origine e dimostrare di avere il permesso di ridistribuirli. Controlla la <link>nostra guida</link> per farlo nel modo giusto!"
},
"project.settings.permissions.info-banner.title": {
"message": "Scopri di più sui permessi di distribuzione"
"message": "Come funzionano le attribuzioni"
},
"project.settings.permissions.learn-more": {
"message": "Scopri di più"
},
"project.settings.permissions.no-results": {
"message": "Nessun file esterno corrisponde alla tua ricerca."
},
"project.settings.permissions.not-allowed.description": {
"message": "Alcuni dei contenuti esterni inclusi non possono essere distribuiti su Modrinth e devono essere rimossi in quanto violano le nostre Regole sui contenuti."
},
"project.settings.permissions.pending-approval-count": {
"message": "{count, plural, =0 {Nessuna attribuzione necessita} one {# attribuzione necessita} other {# attribuzioni necessitano}} di approvazione"
},
"project.settings.permissions.search-placeholder": {
"message": "Cerca tra {count} {count, plural, one {progetto} other {progetti}}..."
},
"project.settings.permissions.sort.most-files": {
"message": "Numero di file"
},
"project.settings.permissions.sort.recently-edited": {
"message": "Modificati di recente"
},
"project.settings.permissions.sort.rejected": {
"message": "Rifiutati"
},
"project.settings.permissions.sort.status": {
"message": "Stato"
"message": "Cerca tra {count, plural, one {# progetto esterno} other {# progetti esterni}}..."
},
"project.settings.title": {
"message": "Impostazioni"
@@ -4028,48 +3938,6 @@
"settings.account.security.email.title": {
"message": "Email"
},
"settings.account.security.passkey.add": {
"message": "Aggiungi passkey"
},
"settings.account.security.passkey.add-modal.name.description": {
"message": "Assicurati di scegliere un nome facile da ricordare, così da poter identificare la passkey più tardi."
},
"settings.account.security.passkey.add-modal.name.label": {
"message": "Nome"
},
"settings.account.security.passkey.add-modal.name.placeholder": {
"message": "Passkey personale"
},
"settings.account.security.passkey.description": {
"message": "Gestisci le tue passkey o aggiungine una nuova."
},
"settings.account.security.passkey.modal.added": {
"message": "Aggiunta {ago}"
},
"settings.account.security.passkey.modal.last-used": {
"message": "Ultimo utilizzo {ago}"
},
"settings.account.security.passkey.modal.loading": {
"message": "Caricando le passkey…"
},
"settings.account.security.passkey.modal.never-used": {
"message": "Mai usata"
},
"settings.account.security.passkey.modal.no-passkeys": {
"message": "Non hai passkey registrate."
},
"settings.account.security.passkey.remove.description": {
"message": "Questo rimuoverà la passkey \"{name}\" permanentemente. Non potrai più accedere con questa."
},
"settings.account.security.passkey.remove.title": {
"message": "Vuoi davvero eliminare questa passkey?"
},
"settings.account.security.passkey.rename-modal.header": {
"message": "Rinomina passkey"
},
"settings.account.security.passkey.title": {
"message": "Gestisci passkey"
},
"settings.account.security.password.action.add": {
"message": "Aggiungi password"
},
@@ -4682,48 +4550,6 @@
"ui.newsletter-button.tooltip": {
"message": "Iscriviti alla newsletter di Modrinth"
},
"version.all-versions": {
"message": "Tutte le versioni"
},
"version.confirm-delete.description": {
"message": "Questa versione verrà eliminata permanentemente. Questa azione non può essere annullata."
},
"version.confirm-delete.proceed": {
"message": "Elimina versione"
},
"version.confirm-delete.title": {
"message": "Vuoi davvero eliminare questa versione?"
},
"version.dependency.view-project": {
"message": "Mostra progetto"
},
"version.dependency.view-version": {
"message": "Mostra versione"
},
"version.download.download-dependency": {
"message": "Scarica dipendenza"
},
"version.download.no-primary-file": {
"message": "Errore: nessun file primario trovato"
},
"version.download.optional-resource-pack": {
"message": "Pacchetto di risorse facoltativo"
},
"version.download.required-resource-pack": {
"message": "Pacchetto di risorse obbligatorio"
},
"version.edit.button": {
"message": "Modifica"
},
"version.edit.details": {
"message": "Modifica dettagli"
},
"version.edit.files": {
"message": "Modifica file"
},
"version.edit.metadata": {
"message": "Modifica metadati"
},
"version.environment.none.description": {
"message": "L'ambiente per questa versione non è stato specificato."
},
@@ -4735,56 +4561,5 @@
},
"version.environment.unknown.title": {
"message": "Ambiente sconosciuto"
},
"version.package-as-mod.button": {
"message": "Impacchetta come mod"
},
"version.package-as-mod.description": {
"message": "Questo creerà una nuova versione con il supporto ai loader selezionati. Sarai reindirizzato alla nuova versione così da modificarla a tuo piacimento."
},
"version.package-as-mod.header": {
"message": "Impacchetta in una mod"
},
"version.package-as-mod.mod-loaders": {
"message": "Loader"
},
"version.package-as-mod.mod-loaders.description": {
"message": "I loader per cui verrà impacchettato il pacchetto di dati."
},
"version.package-as-mod.mod-loaders.placeholder": {
"message": "Scegli i loader..."
},
"version.package-as-mod.submit-button": {
"message": "Impacchetta"
},
"version.section.content.dev-info": {
"message": "Informazioni sullo sviluppatore"
},
"version.section.content.dev-info.gradle-snippet": {
"message": "build.gradle:"
},
"version.section.content.dev-info.maven-coordinates": {
"message": "Coordinate Maven:"
},
"version.section.content.dev-info.maven-description": {
"message": "I progetti su Modrinth sono automaticamente disponibili tramite una repository su Maven per uso con strumenti di sviluppo come <gradle-link>Gradle</gradle-link>. Per saperne di più sull'API Maven di Modrinth, <article-link>clicca qui</article-link>."
},
"version.section.content.dev-info.maven-note": {
"message": "Nota: quando disponibile, dovresti usare la repository Maven dell'autore, in quanto conterrà informazioni transitive sulle dipendenze che l'API Maven di Modrinth non ha. Inoltre, usando altre repository non di Modrinth insieme a queste, potresti finire con dipendenze duplicate, poiché l'identificatore del gruppo risulterà diverso quando fornito dall'API."
},
"version.section.content.dev-info.version-id": {
"message": "ID di versione:"
},
"version.supplementary-resources.copy-hash-sha1": {
"message": "Copia SHA-1"
},
"version.supplementary-resources.copy-hash-sha512": {
"message": "Copia SHA-512"
},
"version.unknown-embedded-content.description": {
"message": "Questa versione è attualmente sospesa e non elencata pubblicamente. Devi dimostrare di avere il permesso di ridistribuire alcuni dei file presenti."
},
"version.unknown-embedded-content.title": {
"message": "Sospesa per attribuzioni incomplete"
}
}
+60 -63
View File
@@ -48,7 +48,7 @@
"message": "メンバー"
},
"analytics.breakdown.monetization": {
"message": "収益の発生"
"message": "収益"
},
"analytics.breakdown.none.selected": {
"message": "分析 - デフォルト"
@@ -102,7 +102,7 @@
"message": "プロジェクトのイベントがありません。"
},
"analytics.chart.controls.previous-period": {
"message": "前の描画"
"message": "1期前の描画"
},
"analytics.chart.controls.project-events": {
"message": "プロジェクト"
@@ -158,15 +158,6 @@
"analytics.filter.search.countries": {
"message": "国を検索…"
},
"analytics.filter.search.dependent-projects": {
"message": "プロジェクトを検索..."
},
"analytics.filter.search.download-sources": {
"message": "ソースを検索..."
},
"analytics.filter.search.members": {
"message": "メンバーを検索..."
},
"analytics.filter.search.project-versions": {
"message": "プロジェクトバージョンを検索…"
},
@@ -176,60 +167,9 @@
"analytics.graph.title.downloads": {
"message": "ダウンロードの推移"
},
"analytics.graph.title.playtime": {
"message": "プレイ時間の推移"
},
"analytics.graph.title.revenue": {
"message": "収益の推移"
},
"analytics.graph.title.views": {
"message": "表示の推移"
},
"analytics.group-by.1h": {
"message": "1時間"
},
"analytics.group-by.6h": {
"message": "6時間"
},
"analytics.group-by.date": {
"message": "日付"
},
"analytics.group-by.day": {
"message": "1日"
},
"analytics.group-by.month": {
"message": "1月"
},
"analytics.group-by.selected.day": {
"message": "日足"
},
"analytics.group-by.selected.hour": {
"message": "1時間足"
},
"analytics.group-by.selected.month": {
"message": "月足"
},
"analytics.group-by.selected.six-hours": {
"message": "6時間足"
},
"analytics.group-by.selected.week": {
"message": "週足"
},
"analytics.group-by.selected.year": {
"message": "年足"
},
"analytics.group-by.week": {
"message": "1週"
},
"analytics.group-by.year": {
"message": "1年"
},
"analytics.loading.fetching-results": {
"message": "データを取得中..."
},
"analytics.options.loading": {
"message": "ロード中..."
},
"analytics.project-event.project-approved": {
"message": "承認済み"
},
@@ -261,7 +201,7 @@
"message": "ダウンロード"
},
"analytics.stat.monetization-banner.body": {
"message": "Modrinthを通じて行われた閲覧およびダウンロードが収益の対象となります。また、Webからの場合は不正防止のためのフィルターを通過する必要があり、アプリからの場合は先述のフィルターに加え、Modrinthにログインされたユーザーに限ります。注意として、すべてのプロジェクトにおいて、収益の対象となる操作の割合はほぼ均一に保たれるため、最終的な収益で不公平が生まれることはありません。"
"message": "Modrinthを通じて行われた閲覧およびダウンロードが収益の対象となります。\nまた、Webからの場合は不正防止のためのフィルターを通過する必要があり、アプリからの場合は先述のフィルターに加え、Modrinthにログインされたユーザーに限ります。\n注意として、すべてのプロジェクトにおいて、収益の対象となる操作の割合はほぼ均一に保たれるため、最終的な収益で不公平が生まれることはありません。"
},
"analytics.stat.monetization-banner.learn-more": {
"message": "もっと詳しく"
@@ -590,9 +530,18 @@
"auth.reset-password.title.long": {
"message": "パスワードを再設定する"
},
"auth.sign-in.2fa.description": {
"message": "続行するには二要素認証コードを入力してください。"
},
"auth.sign-in.2fa.label": {
"message": "二要素認証コードを入力"
},
"auth.sign-in.2fa.placeholder": {
"message": "コードを入力…"
},
"auth.sign-in.sign-in-with": {
"message": "外部アカウントでログイン"
},
"auth.sign-in.title": {
"message": "ログイン"
},
@@ -605,6 +554,9 @@
"auth.sign-up.title": {
"message": "アカウント作成"
},
"auth.sign-up.title.sign-up-with": {
"message": "外部アカウントで作成"
},
"auth.verify-email.action.account-settings": {
"message": "アカウント設定"
},
@@ -1211,6 +1163,9 @@
"dashboard.overview.notifications.loading": {
"message": "通知を読み込み中…"
},
"dashboard.projects.bulk-edit-hint": {
"message": "以下で選択することで、複数のプロジェクトを一括で編集できます。"
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "サーバープロジェクトでは一括編集を利用できません"
},
@@ -1283,6 +1238,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "環境メタデータを確認してください"
},
"dashboard.projects.sort.ascending": {
"message": "昇順"
},
"dashboard.projects.sort.descending": {
"message": "降順"
},
"dashboard.projects.sort.option.name": {
"message": "名前"
},
"dashboard.projects.sort.option.status": {
"message": "ステータス"
},
"dashboard.projects.sort.option.type": {
"message": "種類"
},
"dashboard.projects.table.icon": {
"message": "アイコン"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -2729,21 +2702,45 @@
"project.details.licensed": {
"message": "ライセンス"
},
"project.download.game-version": {
"message": "ゲームバージョン: {version}"
},
"project.download.game-version-error": {
"message": "エラー: ゲームバージョンが見つかりません"
},
"project.download.game-version-tooltip": {
"message": "{title}は{version}でのみ利用可能です"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title}は{platform}の{gameVersion}に対応していません"
},
"project.download.install-with-app": {
"message": "Modrinth Appでインストール"
},
"project.download.no-app": {
"message": "Modrinth Appをお持ちでないですか?"
},
"project.download.no-versions-available": {
"message": "{gameVersion}および{platform}向けのバージョンは利用できません。"
},
"project.download.platform": {
"message": "プラットフォーム: {platform}"
},
"project.download.platform-error": {
"message": "エラー: プラットフォームが見つかりません"
},
"project.download.platform-tooltip": {
"message": "{title}は{platform}でのみ利用可能です"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title}は{gameVersion}の{platform}に対応していません"
},
"project.download.search-game-versions": {
"message": "ゲームバージョンを検索…"
},
"project.download.search-game-versions-label": {
"message": "ゲームバージョンを検索…"
},
"project.download.select-game-version": {
"message": "ゲームバージョンを選択"
},
@@ -266,9 +266,18 @@
"auth.reset-password.title.long": {
"message": "비밀번호 재설정"
},
"auth.sign-in.2fa.description": {
"message": "진행하려면 2단계 인증 코드를 입력해 주세요."
},
"auth.sign-in.2fa.label": {
"message": "2단계 인증 코드 입력"
},
"auth.sign-in.2fa.placeholder": {
"message": "코드 입력..."
},
"auth.sign-in.sign-in-with": {
"message": "다음을 통해 로그인"
},
"auth.sign-in.title": {
"message": "로그인"
},
@@ -281,6 +290,9 @@
"auth.sign-up.title": {
"message": "회원 가입"
},
"auth.sign-up.title.sign-up-with": {
"message": "다음 계정으로 가입"
},
"auth.verify-email.action.account-settings": {
"message": "계정 설정"
},
@@ -998,6 +1010,9 @@
"dashboard.overview.notifications.loading": {
"message": "알림 불러오는 중..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "아래에서 여러 프로젝트를 선택하면 한 번에 편집할 수 있습니다."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "서버 프로젝트는 일괄 편집을 지원하지 않습니다"
},
@@ -1067,6 +1082,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "환경 메타데이터를 검토해 주세요"
},
"dashboard.projects.sort.ascending": {
"message": "오름차순"
},
"dashboard.projects.sort.descending": {
"message": "내림차순"
},
"dashboard.projects.sort.option.name": {
"message": "이름"
},
"dashboard.projects.sort.option.status": {
"message": "상태"
},
"dashboard.projects.sort.option.type": {
"message": "유형"
},
"dashboard.projects.table.icon": {
"message": "아이콘"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -2543,21 +2576,45 @@
"project.details.licensed": {
"message": "라이선스"
},
"project.download.game-version": {
"message": "게임 버전: {version}"
},
"project.download.game-version-error": {
"message": "오류: 게임 버전을 찾을 수 없습니다"
},
"project.download.game-version-tooltip": {
"message": "{title}은(는) {version}에서만 사용할 수 있습니다"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title}은(는) {platform}용 {gameVersion}을(를) 지원하지 않습니다"
},
"project.download.install-with-app": {
"message": "Modrinth App으로 설치"
},
"project.download.no-app": {
"message": "Modrinth 앱을 설치하지 않으셨나요?"
},
"project.download.no-versions-available": {
"message": "{gameVersion} 및 {platform}용 버전이 없습니다."
},
"project.download.platform": {
"message": "플랫폼: {platform}"
},
"project.download.platform-error": {
"message": "오류: 플랫폼을 찾을 수 없음"
},
"project.download.platform-tooltip": {
"message": "{title} 은(는) {platform} 에서만 사용할 수 있습니다"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} 은(는) {gameVersion} 용 {platform} 을(를) 지원하지 않습니다."
},
"project.download.search-game-versions": {
"message": "게임 버전 검색..."
},
"project.download.search-game-versions-label": {
"message": "게임 버전 검색..."
},
"project.download.select-game-version": {
"message": "게임 버전 선택"
},
@@ -2747,15 +2804,45 @@
"project.settings.general.url.title": {
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "다음 파일들을 재배포할 권한이 있음을 증명하는 자료를 제출 바랍니다. 제출 시 보류된 버전은 자동으로 공개됩니다."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "프로젝트 검토를 위해 제출하기 전에 다음 파일들을 재배포할 권한이 있음을 증명하는 자료를 제출해 주세요."
},
"project.settings.permissions.attention-needed.title": {
"message": "알 수 없는 내장 콘텐츠"
},
"project.settings.permissions.completed.description": {
"message": "모든 외부 콘텐츠에는 출처가 명시되어 있습니다."
},
"project.settings.permissions.completed.title": {
"message": "출처 표기 완료됨!"
},
"project.settings.permissions.empty-state.description": {
"message": "어떤 버전에도 외부 콘텐츠가 포함되어 있지 않으므로 권한을 얻는 걸 걱정하지 않아도 됩니다."
},
"project.settings.permissions.empty-state.heading": {
"message": "준비됨!"
},
"project.settings.permissions.fail.description": {
"message": "추가한 외부 콘텐츠 중 일부를 재배포할 권한이 없습니다. Modrinth에 게시하려면 저작권 침해 콘텐츠를 삭제해야 합니다."
},
"project.settings.permissions.fail.title": {
"message": "일부 콘텐츠는 포함될 수 없습니다"
},
"project.settings.permissions.info-banner.description": {
"message": "Modrinth에 호스팅되지 않은 콘텐츠를 포함하는 경우, 해당 콘텐츠의 출처를 알리고 파일 배포 권한이 있는지 확인해야 합니다. <link>저희 지침</link>을 참고해 어떻게 처리하는지 알아보세요!"
},
"project.settings.permissions.info-banner.title": {
"message": "출처 표기 방식 알아보기"
},
"project.settings.permissions.learn-more": {
"message": "더보기"
},
"project.settings.permissions.search-placeholder": {
"message": "{count}개의 {count, plural, other {외부 프로젝트}} 검색..."
},
"project.settings.title": {
"message": "설정"
},
@@ -656,9 +656,18 @@
"auth.reset-password.title.long": {
"message": "Tetap semula kata laluan anda"
},
"auth.sign-in.2fa.description": {
"message": "Sila masukkan kod dua faktor untuk meneruskan."
},
"auth.sign-in.2fa.label": {
"message": "Masukkan kod dua faktor"
},
"auth.sign-in.2fa.placeholder": {
"message": "Masukkan kod..."
},
"auth.sign-in.sign-in-with": {
"message": "Daftar masuk dengan"
},
"auth.sign-in.title": {
"message": "Daftar Masuk"
},
@@ -671,6 +680,9 @@
"auth.sign-up.title": {
"message": "Daftar"
},
"auth.sign-up.title.sign-up-with": {
"message": "Daftar dengan"
},
"auth.verify-email.action.account-settings": {
"message": "Tetapan akaun"
},
@@ -1367,6 +1379,9 @@
"dashboard.overview.notifications.loading": {
"message": "Sedang memuat pemberitahuan..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "Anda boleh menyunting berbilang projek sekaligus dengan memilihnya di bawah."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Projek pelayan tidak menyokong penyuntingan pukal"
},
@@ -1436,6 +1451,24 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Sila semak metadata persekitaran"
},
"dashboard.projects.sort.ascending": {
"message": "Menaik"
},
"dashboard.projects.sort.descending": {
"message": "Menurun"
},
"dashboard.projects.sort.option.name": {
"message": "Nama"
},
"dashboard.projects.sort.option.status": {
"message": "Status"
},
"dashboard.projects.sort.option.type": {
"message": "Jenis"
},
"dashboard.projects.table.icon": {
"message": "Ikon"
},
"dashboard.projects.table.id": {
"message": "ID"
},
@@ -2918,21 +2951,45 @@
"project.details.licensed": {
"message": "Berlesen"
},
"project.download.game-version": {
"message": "Versi permainan: {version}"
},
"project.download.game-version-error": {
"message": "Ralat: tiada versi permainan dijumpai"
},
"project.download.game-version-tooltip": {
"message": "{title} hanya tersedia untuk {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} tidak menyokong {gameVersion} untuk {platform}"
},
"project.download.install-with-app": {
"message": "Pasang dengan Modrinth App"
},
"project.download.no-app": {
"message": "Tidak ada Modrinth App?"
},
"project.download.no-versions-available": {
"message": "Tiada versi tersedia untuk {gameVersion} dan {platform}."
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Ralat: tiada platform dijumpai"
},
"project.download.platform-tooltip": {
"message": "{title} hanya tersedia untuk {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} tidak menyokong {platform} untuk {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Cari versi permainan..."
},
"project.download.search-game-versions-label": {
"message": "Cari versi permainan..."
},
"project.download.select-game-version": {
"message": "Pilih versi permainan"
},
@@ -3122,15 +3179,45 @@
"project.settings.general.url.title": {
"message": "URL"
},
"project.settings.permissions.attention-needed.description.proj-approved": {
"message": "Sila berikan bukti bahawa anda mempunyai kebenaran untuk mengedarkan semula semua fail berikut dan sebarang versi yang ditahan akan diterbitkan secara automatik."
},
"project.settings.permissions.attention-needed.description.proj-draft": {
"message": "Sila berikan bukti bahawa anda mempunyai kebenaran untuk mengedarkan semula semua fail berikut sebelum anda boleh menghantar projek anda untuk semakan."
},
"project.settings.permissions.attention-needed.title": {
"message": "Kandungan terbenam yang tidak diketahui"
},
"project.settings.permissions.completed.description": {
"message": "Semua kandungan luaran mempunyai atribusi yang disediakan."
},
"project.settings.permissions.completed.title": {
"message": "Atribusi selesai!"
},
"project.settings.permissions.empty-state.description": {
"message": "Tiada satu pun versi anda mengandungi kandungan luaran, jadi anda tidak perlu risau tentang mendapatkan kebenaran."
},
"project.settings.permissions.empty-state.heading": {
"message": "Semuanya sudah siap!"
},
"project.settings.permissions.fail.description": {
"message": "Anda tidak mempunyai kebenaran untuk mengedarkan semula beberapa kandungan luaran yang ditambahkan. Untuk menerbitkan projek ini di Modrinth, sila buang kandungan yang melanggar hak."
},
"project.settings.permissions.fail.title": {
"message": "Sesetengah kandungan tidak boleh disertakan"
},
"project.settings.permissions.info-banner.description": {
"message": "Jika anda memasukkan kandungan yang tidak dihoskan di Modrinth, anda perlu memberitahu kami dari mana ia berasal dan mengesahkan bahawa anda mempunyai kebenaran untuk mengedarkan fail tersebut. Lihat <link>panduan kami</link> untuk mengetahui cara untuk melakukannya dengan betul!"
},
"project.settings.permissions.info-banner.title": {
"message": "Ketahui cara atribusi berfungsi"
},
"project.settings.permissions.learn-more": {
"message": "Ketahui lebih lanjut"
},
"project.settings.permissions.search-placeholder": {
"message": "Cari {count} {count, plural, other {projek luaran}}..."
},
"project.settings.title": {
"message": "Tetapan"
},
@@ -617,9 +617,18 @@
"auth.reset-password.title.long": {
"message": "Uw wachtwoord opnieuw instellen"
},
"auth.sign-in.2fa.description": {
"message": "Voer een tweestapsverificatie code in om verder te gaan."
},
"auth.sign-in.2fa.label": {
"message": "Voer twee factor code in"
},
"auth.sign-in.2fa.placeholder": {
"message": "Voer code in..."
},
"auth.sign-in.sign-in-with": {
"message": "Log in met"
},
"auth.sign-in.title": {
"message": "Log in"
},
@@ -629,6 +638,9 @@
"auth.sign-up.title": {
"message": "Aanmelden"
},
"auth.sign-up.title.sign-up-with": {
"message": "Meld je aan met"
},
"auth.verify-email.action.account-settings": {
"message": "Account instellingen"
},
@@ -1358,6 +1370,9 @@
"dashboard.overview.notifications.loading": {
"message": "Notificaties aan het laden..."
},
"dashboard.projects.bulk-edit-hint": {
"message": "U kunt meerdere projecten tegelijkertijd bijwerken door ze hieronder te selecteren."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Massa wijzigingen worden niet ondersteund door server projecten"
},
@@ -1409,6 +1424,12 @@
"dashboard.projects.project.review-environment-metadata": {
"message": "Controller project metadata alstublieft"
},
"dashboard.projects.sort.option.name": {
"message": "Naam"
},
"dashboard.projects.sort.option.status": {
"message": "Status"
},
"dashboard.projects.table.name": {
"message": "Naam"
},
@@ -2792,21 +2813,45 @@
"project.details.licensed": {
"message": "Gelicentieerd"
},
"project.download.game-version": {
"message": "Spel versie: {version}"
},
"project.download.game-version-error": {
"message": "Error: geen spel-versies gevonden"
},
"project.download.game-version-tooltip": {
"message": "{title} is alleen beschikbaar voor {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} steunt niet {gameVersion} voor {platform}"
},
"project.download.install-with-app": {
"message": "Installeer met de Modrinth App"
},
"project.download.no-app": {
"message": "Heb je de Modrinth App niet?"
},
"project.download.no-versions-available": {
"message": "Geen versies beschikbaar voor {gameVersion} en {platform}."
},
"project.download.platform": {
"message": "Platform: {platform}"
},
"project.download.platform-error": {
"message": "Error: geen platformen gevonden"
},
"project.download.platform-tooltip": {
"message": "{title} is alleen beschikbaar voor {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} steunt niet {platform} voor {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Game-versies zoeken..."
},
"project.download.search-game-versions-label": {
"message": "Zoek game-versies..."
},
"project.download.select-game-version": {
"message": "Selecteer game-versie"
},
@@ -254,9 +254,18 @@
"auth.reset-password.title.long": {
"message": "Tilbakestill passordet dit"
},
"auth.sign-in.2fa.description": {
"message": "Vennligst skriv inn en tofaktor-kode for å fortsette."
},
"auth.sign-in.2fa.label": {
"message": "Skriv inn tofaktor-kode"
},
"auth.sign-in.2fa.placeholder": {
"message": "Skriv inn kode..."
},
"auth.sign-in.sign-in-with": {
"message": "Logg inn med"
},
"auth.sign-in.title": {
"message": "Logg inn"
},
@@ -269,6 +278,9 @@
"auth.sign-up.title": {
"message": "Registrer deg"
},
"auth.sign-up.title.sign-up-with": {
"message": "Registrer deg med"
},
"auth.verify-email.action.account-settings": {
"message": "Kontoinstillinger"
},
@@ -851,6 +863,9 @@
"dashboard.organizations.title": {
"message": "Organiseringer"
},
"dashboard.projects.bulk-edit-hint": {
"message": "Du kan redigere flere prosjekt samtidig ved å velge dem nedafor."
},
"dashboard.projects.bulk-edit.server-disabled": {
"message": "Serverprosjekt støtter ikke massredigering"
},
@@ -2249,21 +2264,45 @@
"project.details.licensed": {
"message": "Lisensert"
},
"project.download.game-version": {
"message": "Spillversjon: {version}"
},
"project.download.game-version-error": {
"message": "Error: ingen spillversjon funnet"
},
"project.download.game-version-tooltip": {
"message": "{title} er bare tilgjengelig for {version}"
},
"project.download.game-version-unsupported-tooltip": {
"message": "{title} støtter ikke {gameVersion} for {platform}"
},
"project.download.install-with-app": {
"message": "Installer med Modrinth App"
},
"project.download.no-app": {
"message": "Har du ikke en Modrinth App?"
},
"project.download.no-versions-available": {
"message": "Ingen versjoner tilgjengelige for {gameVersion} og {platform}."
},
"project.download.platform": {
"message": "Plattform: {platform}"
},
"project.download.platform-error": {
"message": "Error: Ingen plattformer funnet"
},
"project.download.platform-tooltip": {
"message": "{title} er bare tilgjengelig for {platform}"
},
"project.download.platform-unsupported-tooltip": {
"message": "{title} støtter ikke {platform} for {gameVersion}"
},
"project.download.search-game-versions": {
"message": "Søk opp spillversjoner..."
},
"project.download.search-game-versions-label": {
"message": "Søk opp spillversjoner..."
},
"project.download.select-game-version": {
"message": "Velg spillversjon"
},

Some files were not shown because too many files have changed in this diff Show More