mirror of
https://github.com/modrinth/code.git
synced 2026-08-01 05:36:39 +00:00
Compare commits
156
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4897cffb8d | ||
|
|
339beddcbc | ||
|
|
73232b3b7f | ||
|
|
bc073581d2 | ||
|
|
4eabe2a126 | ||
|
|
6fc741f7c0 | ||
|
|
24285baebb | ||
|
|
ae939cd4ca | ||
|
|
8faa36b014 | ||
|
|
527757d158 | ||
|
|
f4a2999b20 | ||
|
|
a45a320512 | ||
|
|
586cdc90f9 | ||
|
|
e56f3ae18a | ||
|
|
98c70633ee | ||
|
|
d858901568 | ||
|
|
b9b03796e3 | ||
|
|
c392c85917 | ||
|
|
29110890e1 | ||
|
|
a18f67bf0c | ||
|
|
623e01dbff | ||
|
|
14c728c8e3 | ||
|
|
a1b2073f57 | ||
|
|
45c417e406 | ||
|
|
47c60788ff | ||
|
|
3c60cc6f08 | ||
|
|
d1e8a9c574 | ||
|
|
7bf85239de | ||
|
|
06353bbc14 | ||
|
|
6380dd16d5 | ||
|
|
2376d571bb | ||
|
|
e7926083fb | ||
|
|
e8475086ec | ||
|
|
a686a93858 | ||
|
|
655f899785 | ||
|
|
9bcee5ab52 | ||
|
|
2876890fe5 | ||
|
|
9f050f21e6 | ||
|
|
782c82d3e8 | ||
|
|
da828f4db8 | ||
|
|
4c869d2a37 | ||
|
|
525f60c389 | ||
|
|
042a27a17c | ||
|
|
f6f2cb8d8b | ||
|
|
a747c2f290 | ||
|
|
d756e5ab93 | ||
|
|
1f8b1bd2bb | ||
|
|
23edb2351d | ||
|
|
2c53e1240e | ||
|
|
b7d097d74d | ||
|
|
0a0ef1ce81 | ||
|
|
d7917048d5 | ||
|
|
a0017cf58f | ||
|
|
b188bbf617 | ||
|
|
79ce1adf9d | ||
|
|
c6ef10e798 | ||
|
|
be65589e4d | ||
|
|
86a224419c | ||
|
|
49dd844fe1 | ||
|
|
56536abb24 | ||
|
|
ac5daad280 | ||
|
|
87eec7741b | ||
|
|
4c9ee5b1c4 | ||
|
|
caeab46ca6 | ||
|
|
8e6004fdd5 | ||
|
|
50b2b9567c | ||
|
|
e0ea14226e | ||
|
|
d2b85c9f8e | ||
|
|
d33f00d2b1 | ||
|
|
b3257a0614 | ||
|
|
486b467af2 | ||
|
|
336050f4df | ||
|
|
2bb1ef775c | ||
|
|
5ed322d281 | ||
|
|
8591bc8ebc | ||
|
|
01fd18d550 | ||
|
|
3aaa2ef071 | ||
|
|
a3aeeac2c3 | ||
|
|
dfba212be4 | ||
|
|
3877999e53 | ||
|
|
46a7cf490d | ||
|
|
b5f7406998 | ||
|
|
3679d2c786 | ||
|
|
1a0633b6db | ||
|
|
3ae50a2abb | ||
|
|
408927a23e | ||
|
|
3c29a62036 | ||
|
|
407aa4b4cf | ||
|
|
d709bbeb54 | ||
|
|
b5662371d8 | ||
|
|
db9689b335 | ||
|
|
0f1483d9df | ||
|
|
d48ad873fb | ||
|
|
ff7c0cb5b8 | ||
|
|
faed8c3f6d | ||
|
|
63d00cb660 | ||
|
|
27d54a98af | ||
|
|
05a1a10788 | ||
|
|
addb6c6045 | ||
|
|
e08da23fb5 | ||
|
|
ecd69f88b2 | ||
|
|
d834ce3645 | ||
|
|
cf58559381 | ||
|
|
01a7ee8d1e | ||
|
|
5480826a53 | ||
|
|
9184de92df | ||
|
|
d753b8735c | ||
|
|
c7a5336a51 | ||
|
|
292b8f4cd2 | ||
|
|
c391fb12b9 | ||
|
|
62f20b9aee | ||
|
|
2aca44bbcc | ||
|
|
b9be6abf0b | ||
|
|
a5be0114c5 | ||
|
|
f32f732c31 | ||
|
|
e974672e71 | ||
|
|
d6356ea8e5 | ||
|
|
872dcbcf78 | ||
|
|
44da0ed5ff | ||
|
|
ec7c893681 | ||
|
|
3ce8f85463 | ||
|
|
4ce82b01e6 | ||
|
|
f4d65437df | ||
|
|
9646083025 | ||
|
|
652a9b0b5c | ||
|
|
918c4a9c83 | ||
|
|
0a0b19233d | ||
|
|
6f2e04c3de | ||
|
|
ece35f9158 | ||
|
|
3ccb8132d8 | ||
|
|
cad6bd4f92 | ||
|
|
65bd8937a6 | ||
|
|
abcac5f40a | ||
|
|
43893158ac | ||
|
|
fcab720943 | ||
|
|
4183702a72 | ||
|
|
8946e4c394 | ||
|
|
f1ce31001a | ||
|
|
c99564f8ea | ||
|
|
71c1deb5cd | ||
|
|
042dde075b | ||
|
|
3bfe670d47 | ||
|
|
ed53fe5330 | ||
|
|
547921badc | ||
|
|
aac61dcfaa | ||
|
|
52a972d949 | ||
|
|
bdc2b4abf8 | ||
|
|
c19ce11ea4 | ||
|
|
c54906e6de | ||
|
|
57b4f21080 | ||
|
|
66f3c39c13 | ||
|
|
7484afa18e | ||
|
|
7ea0635d86 | ||
|
|
fe3aba52ab | ||
|
|
64f87551ff | ||
|
|
c8a586c6f1 |
@@ -81,6 +81,6 @@ Please follow these rules precisely:
|
||||
Use existing patterns from our codebase:
|
||||
|
||||
- Variables/plurals: see `apps/frontend/src/pages/frog.vue`
|
||||
- Rich-text link tags: see `apps/frontend/src/pages/auth/welcome.vue` and `apps/frontend/src/error.vue`
|
||||
- Rich-text link tags: see `apps/frontend/src/error.vue`
|
||||
|
||||
When you finish, there should be no hard-coded English strings left in the template—everything comes from `formatMessage` or `<IntlFormatted>`.
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: taiki-e/install-action@b5fddbb5361bce8a06fb168c9d403a6cc552b084 # v2.75.29
|
||||
- uses: taiki-e/install-action@fd2f5e3d644b484055ebf4268f474c565f148f25 # v2.81.9
|
||||
with:
|
||||
tool: tombi
|
||||
- run: tombi lint
|
||||
|
||||
@@ -20,6 +20,14 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
description: 'The environment to deploy to (staging-preview or production-preview)'
|
||||
head_sha:
|
||||
required: false
|
||||
type: string
|
||||
description: 'The commit SHA to deploy (defaults to github.sha)'
|
||||
head_ref:
|
||||
required: false
|
||||
type: string
|
||||
description: 'The branch name to deploy (defaults to github.ref_name)'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.environment || 'push' }}
|
||||
@@ -40,12 +48,14 @@ jobs:
|
||||
|
||||
- name: Configure environment
|
||||
id: meta
|
||||
env:
|
||||
ENV_INPUT: ${{ inputs.environment }}
|
||||
REF: ${{ github.ref }}
|
||||
DEPLOY_SHA: ${{ inputs.head_sha || github.sha }}
|
||||
run: |
|
||||
echo "cmd=deploy" >> $GITHUB_OUTPUT
|
||||
|
||||
ENV_INPUT="${{ inputs.environment }}"
|
||||
REF="${{ github.ref }}"
|
||||
SHA_SHORT="${GITHUB_SHA::8}"
|
||||
SHA_SHORT="${DEPLOY_SHA::8}"
|
||||
|
||||
if [ "$ENV_INPUT" = "staging-preview" ]; then
|
||||
echo "env=staging" >> $GITHUB_OUTPUT
|
||||
@@ -106,8 +116,8 @@ jobs:
|
||||
working-directory: ./apps/frontend
|
||||
run: pnpm build
|
||||
env:
|
||||
CF_PAGES_BRANCH: ${{ github.ref_name }}
|
||||
CF_PAGES_COMMIT_SHA: ${{ github.sha }}
|
||||
CF_PAGES_BRANCH: ${{ inputs.head_ref || github.ref_name }}
|
||||
CF_PAGES_COMMIT_SHA: ${{ inputs.head_sha || github.sha }}
|
||||
CF_PAGES_URL: ${{ steps.meta.outputs.url }}
|
||||
BUILD_ENV: ${{ steps.meta.outputs.env }}
|
||||
PREVIEW: ${{ inputs.environment != '' && 'true' || 'false' }}
|
||||
|
||||
@@ -24,6 +24,8 @@ jobs:
|
||||
environment: [staging-preview, production-preview]
|
||||
with:
|
||||
environment: ${{ matrix.environment }}
|
||||
head_sha: ${{ github.event.pull_request.head.sha }}
|
||||
head_ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
deploy-storybook:
|
||||
if: github.repository_owner == 'modrinth' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
@@ -68,7 +70,9 @@ jobs:
|
||||
|
||||
- name: Configure short SHA
|
||||
id: meta
|
||||
run: echo "sha_short=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: echo "sha_short=${HEAD_SHA::8}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Deploy Storybook preview
|
||||
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3.15.0
|
||||
@@ -93,6 +97,8 @@ jobs:
|
||||
|
||||
- name: Read deployment URLs
|
||||
id: urls
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
STAGING_PREVIEW_URL=$(cat deployment-url-staging-preview.txt)
|
||||
PRODUCTION_PREVIEW_URL=$(cat deployment-url-production-preview.txt)
|
||||
@@ -102,7 +108,7 @@ jobs:
|
||||
|
||||
echo "staging-preview-url=$STAGING_PREVIEW_URL" >> $GITHUB_OUTPUT
|
||||
echo "production-preview-url=$PRODUCTION_PREVIEW_URL" >> $GITHUB_OUTPUT
|
||||
echo "storybook-preview-url=https://git-${GITHUB_SHA::8}-storybook.modrinth.workers.dev" >> $GITHUB_OUTPUT
|
||||
echo "storybook-preview-url=https://git-${HEAD_SHA::8}-storybook.modrinth.workers.dev" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find comment
|
||||
if: github.event_name == 'pull_request'
|
||||
@@ -123,7 +129,7 @@ jobs:
|
||||
body: |
|
||||
## Frontend previews
|
||||
|
||||
Last deployed commit is [${{ github.sha }}](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.sha }})
|
||||
Last deployed commit is [${{ github.event.pull_request.head.sha }}](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.event.pull_request.head.sha }})
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
|
||||
@@ -99,6 +99,12 @@ jobs:
|
||||
- name: Setup mold
|
||||
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
|
||||
|
||||
- name: Install build dependencies
|
||||
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
|
||||
with:
|
||||
|
||||
@@ -99,8 +99,8 @@ jobs:
|
||||
- name: Install build dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
version: v1 # cache key
|
||||
packages: cmake libcurl4-openssl-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
version: v2 # cache key
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 # v1.18.1
|
||||
|
||||
- name: Setup nextest
|
||||
run: cargo binstall --no-confirm --secure cargo-nextest@0.9.133
|
||||
run: cargo binstall --no-confirm --secure --locked cargo-nextest@0.9.133
|
||||
|
||||
# cargo-binstall does not have pre-built binaries for sqlx-cli, so we fall
|
||||
# back to a cached cargo install
|
||||
|
||||
Vendored
+1
-6
@@ -1,12 +1,7 @@
|
||||
{
|
||||
"prettier.endOfLine": "lf",
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
|
||||
"editor.detectIndentation": false,
|
||||
"editor.insertSpaces": false,
|
||||
"files.eol": "\n",
|
||||
|
||||
@@ -50,7 +50,7 @@ Run these from the **root** folder before opening a pull request - do not run th
|
||||
- **App frontend:** `pnpm prepr:frontend:app`
|
||||
- **Frontend libs:** `pnpm prepr:frontend:lib`
|
||||
- **All frontend (app+web):** `pnpm prepr`
|
||||
- **Labrinth (backend):** See `apps/labrinth/CLAUDE.md`
|
||||
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
|
||||
|
||||
The website and app `prepr` commands
|
||||
|
||||
@@ -62,7 +62,7 @@ The website and app `prepr` commands
|
||||
|
||||
## Project-Specific Instructions
|
||||
|
||||
Each project may have its own `CLAUDE.md` with detailed instructions:
|
||||
Each project may have its own file with detailed instructions:
|
||||
|
||||
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
|
||||
- [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) - Frontend Website
|
||||
|
||||
Generated
+472
-44
@@ -477,7 +477,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
@@ -494,7 +494,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"utoipa",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -530,6 +530,45 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
|
||||
dependencies = [
|
||||
"asn1-rs-derive",
|
||||
"asn1-rs-impl",
|
||||
"displaydoc",
|
||||
"nom 7.1.3",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-derive"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-impl"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "astral-tokio-tar"
|
||||
version = "0.5.6"
|
||||
@@ -1000,7 +1039,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tracing",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1369,6 +1408,17 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||
|
||||
[[package]]
|
||||
name = "base64urlsafedata"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b08e33815c87d8cadcddb1e74ac307368a3751fbe40c961538afa21a1899f21c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"pastey",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
@@ -1831,7 +1881,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fnv",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1856,6 +1906,17 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chardetng"
|
||||
version = "0.1.17"
|
||||
@@ -2004,7 +2065,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2332,6 +2393,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.3.0"
|
||||
@@ -2706,7 +2776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2726,6 +2796,20 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"displaydoc",
|
||||
"nom 7.1.3",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.4"
|
||||
@@ -3871,11 +3955,25 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 5.3.0",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.3"
|
||||
@@ -4720,6 +4818,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -4817,7 +4921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -5277,6 +5381,7 @@ dependencies = [
|
||||
"quick-xml 0.38.3",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rdkafka",
|
||||
"redis",
|
||||
"regex",
|
||||
"reqwest 0.12.24",
|
||||
@@ -5307,8 +5412,10 @@ dependencies = [
|
||||
"utoipa",
|
||||
"utoipa-actix-web",
|
||||
"utoipa-scalar",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"validator",
|
||||
"webauthn-rs",
|
||||
"webauthn-rs-proto",
|
||||
"webp",
|
||||
"woothee",
|
||||
"yaserde",
|
||||
@@ -5341,6 +5448,12 @@ dependencies = [
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
@@ -5489,6 +5602,18 @@ dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -5742,7 +5867,7 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"tokio",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"yaup",
|
||||
@@ -5882,7 +6007,7 @@ dependencies = [
|
||||
"rustc_version",
|
||||
"smallvec",
|
||||
"tagptr",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5933,7 +6058,7 @@ dependencies = [
|
||||
"serde_with",
|
||||
"strum",
|
||||
"utoipa",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6568,6 +6693,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -6598,15 +6732,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.73"
|
||||
version = "0.10.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
@@ -6630,9 +6763,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.109"
|
||||
version = "0.9.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -6936,6 +7069,12 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pastey"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||
|
||||
[[package]]
|
||||
name = "path-util"
|
||||
version = "0.0.0"
|
||||
@@ -7764,6 +7903,12 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "r2d2"
|
||||
version = "0.8.10"
|
||||
@@ -7816,6 +7961,17 @@ dependencies = [
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.2",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@@ -7873,6 +8029,12 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@@ -7967,6 +8129,37 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdkafka"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1beea247b9a7600a81d4cc33f659ce1a77e1988323d7d2809c7ed1c21f4c316d"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"log",
|
||||
"rdkafka-sys",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"slab",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdkafka-sys"
|
||||
version = "4.10.0+2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e234cf318915c1059d4921ef7f75616b5219b10b46e9f3a511a15eb4b56a3f77"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"num_enum",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.32.7"
|
||||
@@ -8269,7 +8462,7 @@ dependencies = [
|
||||
"rkyv_derive",
|
||||
"seahash",
|
||||
"tinyvec",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8408,6 +8601,15 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||
dependencies = [
|
||||
"nom 7.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@@ -8652,7 +8854,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8927,7 +9129,7 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8984,6 +9186,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor_2"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aec2709de9078e077090abd848e967abab63c9fb3fdb5d4799ad359d8d482c"
|
||||
dependencies = [
|
||||
"half 2.7.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
@@ -9230,7 +9442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
@@ -9247,7 +9459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
@@ -9503,7 +9715,7 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
@@ -9586,7 +9798,7 @@ dependencies = [
|
||||
"stringprep",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@@ -9626,7 +9838,7 @@ dependencies = [
|
||||
"stringprep",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@@ -9653,7 +9865,7 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10118,7 +10330,7 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -10426,7 +10638,7 @@ dependencies = [
|
||||
"toml 0.9.8",
|
||||
"url",
|
||||
"urlpattern",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -10582,7 +10794,7 @@ dependencies = [
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"whoami",
|
||||
"windows",
|
||||
"windows-core 0.61.2",
|
||||
@@ -10630,7 +10842,7 @@ dependencies = [
|
||||
"tracing-error",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows-core 0.61.2",
|
||||
@@ -11119,9 +11331,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -11139,14 +11351,14 @@ dependencies = [
|
||||
"mutually_exclusive_features",
|
||||
"pin-project",
|
||||
"tracing",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11155,9 +11367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -11553,7 +11765,7 @@ dependencies = [
|
||||
"regex",
|
||||
"syn 2.0.106",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11579,14 +11791,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.1"
|
||||
version = "1.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"rand 0.10.1",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -11761,7 +11973,16 @@ version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.46.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11829,6 +12050,28 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 2.11.4",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.4.2"
|
||||
@@ -11855,6 +12098,18 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.11.4",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.11"
|
||||
@@ -11935,6 +12190,74 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webauthn-attestation-ca"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6475c0bbd1a3f04afaa3e98880408c5be61680c5e6bd3c6f8c250990d5d3e18e"
|
||||
dependencies = [
|
||||
"base64urlsafedata",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
"serde",
|
||||
"tracing",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webauthn-rs"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c548915e0e92ee946bbf2aecf01ea21bef53d974b0793cc6732ba81a03fc422"
|
||||
dependencies = [
|
||||
"base64urlsafedata",
|
||||
"serde",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.23.3",
|
||||
"webauthn-rs-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webauthn-rs-core"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "296d2d501feb715d80b8e186fb88bab1073bca17f460303a1013d17b673bea6a"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"base64urlsafedata",
|
||||
"der-parser",
|
||||
"hex",
|
||||
"nom 7.1.3",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"serde",
|
||||
"serde_cbor_2",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.23.3",
|
||||
"webauthn-attestation-ca",
|
||||
"webauthn-rs-proto",
|
||||
"x509-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webauthn-rs-proto"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c37393beac9c1ed1ca6dbb30b1e01783fb316ab3a45d90ecd48c99052dd7ef1e"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"base64urlsafedata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "2.0.2"
|
||||
@@ -12657,6 +12980,94 @@ version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.5.0",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.5.0",
|
||||
"indexmap 2.11.4",
|
||||
"prettyplease",
|
||||
"syn 2.0.106",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
"indexmap 2.11.4",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap 2.11.4",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "woothee"
|
||||
version = "0.13.0"
|
||||
@@ -12748,6 +13159,23 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"data-encoding",
|
||||
"der-parser",
|
||||
"lazy_static",
|
||||
"nom 7.1.3",
|
||||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.6.1"
|
||||
|
||||
@@ -140,6 +140,7 @@ quick-xml = "0.38.3"
|
||||
quote = { version = "1.0" }
|
||||
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
|
||||
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
||||
rdkafka = { version = "0.36.2", features = ["cmake-build"] }
|
||||
redis = "0.32.7"
|
||||
regex = "1.12.2"
|
||||
reqwest = { version = "0.12.24", default-features = false }
|
||||
@@ -219,6 +220,8 @@ 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"
|
||||
webauthn-rs-proto = "0.5.5"
|
||||
webp = { version = "0.3.1", default-features = false }
|
||||
webview2-com = "0.38.0" # Should be updated in lockstep with wry
|
||||
whoami = "1.6.1"
|
||||
@@ -269,6 +272,7 @@ redundant_type_annotations = "warn"
|
||||
result_large_err = "allow"
|
||||
todo = "warn"
|
||||
too_many_arguments = "allow"
|
||||
type_complexity = "allow"
|
||||
uninlined_format_args = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
wildcard_dependencies = "warn"
|
||||
|
||||
@@ -89,14 +89,9 @@ const initFiles = async () => {
|
||||
disabled:
|
||||
folder === 'profile.json' ||
|
||||
folder.startsWith('modrinth_logs') ||
|
||||
folder.startsWith('.fabric'),
|
||||
folder.startsWith('.fabric') ||
|
||||
folder.startsWith('__MACOSX'),
|
||||
}))
|
||||
.filter(
|
||||
(pathData) =>
|
||||
!pathData.path.includes('.DS_Store') &&
|
||||
pathData.path !== 'mods/.connector' &&
|
||||
!pathData.path.startsWith('mods/.connector/'),
|
||||
)
|
||||
.forEach((pathData) => {
|
||||
const parent = pathData.path.split(sep).slice(0, -1).join(sep)
|
||||
if (parent !== '') {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import type { GameVersionTag, PlatformTag } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_versions, get_version } from '@/helpers/cache'
|
||||
@@ -46,55 +46,62 @@ debug('metadata load: start', {
|
||||
installStage: instance.value.install_stage,
|
||||
})
|
||||
|
||||
const [
|
||||
fabric_versions,
|
||||
forge_versions,
|
||||
quilt_versions,
|
||||
neoforge_versions,
|
||||
all_game_versions,
|
||||
loaders,
|
||||
] = await Promise.all([
|
||||
get_loader_versions('fabric')
|
||||
.then((manifest: Manifest) => shallowRef(manifest))
|
||||
.catch(handleError),
|
||||
get_loader_versions('forge')
|
||||
.then((manifest: Manifest) => shallowRef(manifest))
|
||||
.catch(handleError),
|
||||
get_loader_versions('quilt')
|
||||
.then((manifest: Manifest) => shallowRef(manifest))
|
||||
.catch(handleError),
|
||||
get_loader_versions('neo')
|
||||
.then((manifest: Manifest) => shallowRef(manifest))
|
||||
.catch(handleError),
|
||||
get_game_versions()
|
||||
.then((gameVersions: GameVersionTag[]) => shallowRef(gameVersions))
|
||||
.catch(handleError),
|
||||
get_loaders()
|
||||
.then((value: PlatformTag[]) =>
|
||||
value
|
||||
.filter(
|
||||
(item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla',
|
||||
)
|
||||
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
|
||||
)
|
||||
.then((loader: PlatformTag[]) => ref(loader))
|
||||
.catch(handleError),
|
||||
])
|
||||
function getSupportedModpackLoaders() {
|
||||
return get_loaders().then((value: PlatformTag[]) =>
|
||||
value
|
||||
.filter((item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla')
|
||||
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
|
||||
)
|
||||
}
|
||||
|
||||
debug('metadata load: done', {
|
||||
hasFabricManifest: !!fabric_versions?.value,
|
||||
hasForgeManifest: !!forge_versions?.value,
|
||||
hasQuiltManifest: !!quilt_versions?.value,
|
||||
hasNeoforgeManifest: !!neoforge_versions?.value,
|
||||
gameVersions: all_game_versions?.value?.length ?? 0,
|
||||
availablePlatforms: loaders?.value?.map((loader) => loader.name) ?? [],
|
||||
const fabricVersionsQuery = useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'fabric'],
|
||||
queryFn: () => get_loader_versions('fabric') as Promise<Manifest>,
|
||||
})
|
||||
const forgeVersionsQuery = useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'forge'],
|
||||
queryFn: () => get_loader_versions('forge') as Promise<Manifest>,
|
||||
})
|
||||
const quiltVersionsQuery = useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'quilt'],
|
||||
queryFn: () => get_loader_versions('quilt') as Promise<Manifest>,
|
||||
})
|
||||
const neoforgeVersionsQuery = useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'neo'],
|
||||
queryFn: () => get_loader_versions('neo') as Promise<Manifest>,
|
||||
})
|
||||
const gameVersionsQuery = useQuery({
|
||||
queryKey: ['instance-settings', 'game-versions'],
|
||||
queryFn: () => get_game_versions() as Promise<GameVersionTag[]>,
|
||||
})
|
||||
const loadersQuery = useQuery({
|
||||
queryKey: ['instance-settings', 'loaders', 'modpack'],
|
||||
queryFn: getSupportedModpackLoaders,
|
||||
})
|
||||
|
||||
const { data: modpackInfo } = useQuery({
|
||||
const metadataLoading = computed(() =>
|
||||
[
|
||||
fabricVersionsQuery,
|
||||
forgeVersionsQuery,
|
||||
quiltVersionsQuery,
|
||||
neoforgeVersionsQuery,
|
||||
gameVersionsQuery,
|
||||
loadersQuery,
|
||||
].some((query) => query.isLoading.value),
|
||||
)
|
||||
|
||||
debug('metadata queries configured', {
|
||||
instancePath: instance.value.path,
|
||||
loader: instance.value.loader,
|
||||
gameVersion: instance.value.game_version,
|
||||
})
|
||||
|
||||
const modpackInfoQuery = useQuery({
|
||||
queryKey: computed(() => ['linkedModpackInfo', instance.value.path]),
|
||||
queryFn: () => get_linked_modpack_info(instance.value.path, 'must_revalidate'),
|
||||
enabled: computed(() => !!instance.value.linked_data?.project_id && !offline),
|
||||
})
|
||||
const modpackInfo = modpackInfoQuery.data
|
||||
|
||||
const repairing = ref(false)
|
||||
const reinstalling = ref(false)
|
||||
@@ -107,17 +114,17 @@ const messages = defineMessages({
|
||||
})
|
||||
|
||||
function getManifest(loader: string) {
|
||||
const map: Record<string, typeof fabric_versions> = {
|
||||
fabric: fabric_versions,
|
||||
forge: forge_versions,
|
||||
quilt: quilt_versions,
|
||||
neoforge: neoforge_versions,
|
||||
const map: Record<string, Manifest | undefined> = {
|
||||
fabric: fabricVersionsQuery.data.value,
|
||||
forge: forgeVersionsQuery.data.value,
|
||||
quilt: quiltVersionsQuery.data.value,
|
||||
neoforge: neoforgeVersionsQuery.data.value,
|
||||
}
|
||||
const manifest = map[loader]
|
||||
debug('getManifest:', {
|
||||
loader,
|
||||
hasManifest: !!manifest?.value,
|
||||
gameVersions: manifest?.value?.gameVersions?.length ?? 0,
|
||||
hasManifest: !!manifest,
|
||||
gameVersions: manifest?.gameVersions?.length ?? 0,
|
||||
})
|
||||
return manifest
|
||||
}
|
||||
@@ -143,7 +150,7 @@ provideAppBackup({
|
||||
|
||||
provideInstallationSettings({
|
||||
closeSettings: closeModal,
|
||||
loading: ref(false),
|
||||
loading: computed(() => metadataLoading.value || modpackInfoQuery.isLoading.value),
|
||||
installationInfo: computed(() => {
|
||||
const rows = [
|
||||
{
|
||||
@@ -185,14 +192,14 @@ provideInstallationSettings({
|
||||
currentPlatform: computed(() => instance.value.loader),
|
||||
currentGameVersion: computed(() => instance.value.game_version),
|
||||
currentLoaderVersion: computed(() => instance.value.loader_version ?? ''),
|
||||
availablePlatforms: loaders?.value?.map((x) => x.name) ?? [],
|
||||
availablePlatforms: computed(() => loadersQuery.data.value?.map((x) => x.name) ?? []),
|
||||
|
||||
resolveGameVersions(loader, showSnapshots) {
|
||||
const versions = all_game_versions?.value ?? []
|
||||
const versions = gameVersionsQuery.data.value ?? []
|
||||
const filtered = versions.filter((item) => {
|
||||
if (loader === 'vanilla') return true
|
||||
const manifest = getManifest(loader)
|
||||
return !!manifest?.value?.gameVersions?.some((x) => item.version === x.id)
|
||||
return !!manifest?.gameVersions?.some((x) => item.version === x.id)
|
||||
})
|
||||
const result = (
|
||||
showSnapshots ? filtered : filtered.filter((x) => x.version_type === 'release')
|
||||
@@ -213,12 +220,12 @@ provideInstallationSettings({
|
||||
return []
|
||||
}
|
||||
const manifest = getManifest(loader)
|
||||
if (!manifest?.value) {
|
||||
if (!manifest) {
|
||||
debug('resolveLoaderVersions: no manifest', { loader, gameVersion })
|
||||
return []
|
||||
}
|
||||
if (loader === 'fabric' || loader === 'quilt') {
|
||||
const result = manifest.value.gameVersions[0]?.loaders ?? []
|
||||
const result = manifest.gameVersions[0]?.loaders ?? []
|
||||
debug('resolveLoaderVersions: fabric/quilt result', {
|
||||
loader,
|
||||
gameVersion,
|
||||
@@ -226,14 +233,13 @@ provideInstallationSettings({
|
||||
})
|
||||
return result
|
||||
}
|
||||
const result =
|
||||
manifest.value.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? []
|
||||
const result = manifest.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? []
|
||||
debug('resolveLoaderVersions: result', { loader, gameVersion, count: result.length })
|
||||
return result
|
||||
},
|
||||
|
||||
resolveHasSnapshots(loader) {
|
||||
const versions = all_game_versions?.value ?? []
|
||||
const versions = gameVersionsQuery.data.value ?? []
|
||||
if (loader === 'vanilla') {
|
||||
const result = versions.some((x) => x.version_type !== 'release')
|
||||
debug('resolveHasSnapshots: vanilla', { loader, result })
|
||||
@@ -241,7 +247,7 @@ provideInstallationSettings({
|
||||
}
|
||||
const manifest = getManifest(loader)
|
||||
const supported = versions.filter(
|
||||
(item) => !!manifest?.value?.gameVersions?.some((x) => item.version === x.id),
|
||||
(item) => !!manifest?.gameVersions?.some((x) => item.version === x.id),
|
||||
)
|
||||
const result = supported.some((x) => x.version_type !== 'release')
|
||||
debug('resolveHasSnapshots:', {
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
type TabbedModalTab,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import type { PlatformTag } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
|
||||
@@ -26,7 +27,9 @@ import InstallationSettings from '@/components/ui/instance_settings/Installation
|
||||
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
|
||||
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
|
||||
import { get_project_v3 } from '@/helpers/cache'
|
||||
import { get_loader_versions } from '@/helpers/metadata'
|
||||
import { get_linked_modpack_info } from '@/helpers/profile'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import { provideInstanceSettings } from '@/providers/instance-settings'
|
||||
|
||||
import type { GameInstance } from '../../../helpers/types'
|
||||
@@ -45,7 +48,6 @@ const isMinecraftServer = ref(false)
|
||||
const handleUnlinked = () => emit('unlinked')
|
||||
|
||||
const instanceRef = computed(() => props.instance)
|
||||
const queryClient = useQueryClient()
|
||||
const tabbedModal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
|
||||
function hide() {
|
||||
@@ -120,13 +122,46 @@ const tabs = computed<TabbedModalTab[]>(() => [
|
||||
},
|
||||
])
|
||||
|
||||
function getSupportedModpackLoaders() {
|
||||
return get_loaders().then((value: PlatformTag[]) =>
|
||||
value
|
||||
.filter((item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla')
|
||||
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
|
||||
)
|
||||
}
|
||||
|
||||
// Preload
|
||||
useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'fabric'],
|
||||
queryFn: () => get_loader_versions('fabric'),
|
||||
})
|
||||
useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'forge'],
|
||||
queryFn: () => get_loader_versions('forge'),
|
||||
})
|
||||
useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'quilt'],
|
||||
queryFn: () => get_loader_versions('quilt'),
|
||||
})
|
||||
useQuery({
|
||||
queryKey: ['instance-settings', 'loader-versions', 'neo'],
|
||||
queryFn: () => get_loader_versions('neo'),
|
||||
})
|
||||
useQuery({
|
||||
queryKey: ['instance-settings', 'game-versions'],
|
||||
queryFn: get_game_versions,
|
||||
})
|
||||
useQuery({
|
||||
queryKey: ['instance-settings', 'loaders', 'modpack'],
|
||||
queryFn: getSupportedModpackLoaders,
|
||||
})
|
||||
useQuery({
|
||||
queryKey: computed(() => ['linkedModpackInfo', props.instance.path]),
|
||||
queryFn: () => get_linked_modpack_info(props.instance.path, 'stale_while_revalidate'),
|
||||
enabled: computed(() => !!props.instance.linked_data?.project_id && !props.offline),
|
||||
})
|
||||
|
||||
function show(tabIndex?: number) {
|
||||
if (props.instance.linked_data?.project_id) {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['linkedModpackInfo', props.instance.path],
|
||||
queryFn: () => get_linked_modpack_info(props.instance.path, 'stale_while_revalidate'),
|
||||
})
|
||||
}
|
||||
tabbedModal.value?.show()
|
||||
if (tabIndex !== undefined) {
|
||||
nextTick(() => tabbedModal.value?.setTab(tabIndex))
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "جاز تنزيل إصدار جافا {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "تحميل التحديث"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "التنزيلات"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "النسخة ألرئيسية"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "أعد التشغيل للتحديث"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "عرض المزيد من النسخ قيد التشغيل"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "إيقاف النسخة"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "تحديث"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "عرض التحميلات الفعالة"
|
||||
},
|
||||
@@ -35,12 +44,36 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "عرض التسجيل"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "يتيح العرض المتقدم لتأثيرات مثل التشويش الذي قد تسبب مشكلات في الأداء دون استخدام العرض المسرع بالأجهزة."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "العرض المتقدم"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "اختر لون سمتك المفضل من أجل تطبيق Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "لون السمة"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "غير الصفحة التي يفتح عليها المشغل."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "صفحة الرئيسية"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "مكتبة"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "الصفحة الرئيسة الافتراضية"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "ألغ تفعيل عرض الاسم فوق لاعبك في صفحة المظاهر."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "إخفاء عرض الاسم"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "قد تكون خوادم مصادقة ماينكرافت معطلة حاليًا. تحقق من اتصالك بالإنترنت وحاول مرة أخرى لاحقًا."
|
||||
},
|
||||
@@ -59,15 +92,27 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "مضاف فعلا"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "العودة للنموذج"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "استكشف محتوى"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "استكشف خوادم"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "إخفاء الخوادم المضافة مسبقًا"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "جاري التثبيت"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "تنزيل الحزم"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "أدخل وصف التعديل..."
|
||||
},
|
||||
@@ -83,6 +128,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "إسم حزمة التعديل"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "أختيار الملفات التي سيتم تصديرها"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "رقم الإصدار"
|
||||
},
|
||||
@@ -102,13 +150,13 @@
|
||||
"message": "حذف النموذج"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "حُزْمَة التعديل هذه مثبته فعلًا في نموذج <bold>{instanceName}</bold>. هل انت متأكد بإرادة نسخه؟"
|
||||
"message": "حُزْمَة التعديل مثبته مسبقًا فعلًا في نموذج <bold>{instanceName}</bold>. هل أنت متأكد بإرادة نسخه؟"
|
||||
},
|
||||
"app.instance.modpack-already-installed.create": {
|
||||
"message": "إنشاء"
|
||||
},
|
||||
"app.instance.modpack-already-installed.header": {
|
||||
"message": "حُزْمَة التعديل مثبتة بالفعل"
|
||||
"message": "حُزْمَة التعديل مثبتة فعلًا"
|
||||
},
|
||||
"app.instance.modpack-already-installed.instance": {
|
||||
"message": "النموذج"
|
||||
@@ -126,7 +174,7 @@
|
||||
"message": "تحقق من المشاريع التي أستخدمها في حزمة التعديل الخاص بي!"
|
||||
},
|
||||
"app.instance.mods.share-title": {
|
||||
"message": "مشاركة محتوى حزمة التعديل"
|
||||
"message": "مشاركة محتوى حُزْمَة التعديل"
|
||||
},
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "تم الرفع بنجاح"
|
||||
@@ -189,7 +237,7 @@
|
||||
"message": "{count, plural, one {# تعديل} other {# تعديلات}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "حزمة التعديل مطلوبة"
|
||||
"message": "حُزْمَة التعديل مطلوبة"
|
||||
},
|
||||
"app.modal.install-to-play.server-requires-mods": {
|
||||
"message": "يتطلب هذا الخادم تعديلات للعب. انقر فوق \"تثبيت\" لإعداد الملفات المطلوبة من Modrinth، ثم قم بتشغيله مباشرة إلى الخادم."
|
||||
@@ -210,7 +258,13 @@
|
||||
"message": "يلزم التحديث"
|
||||
},
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى آخر اصدار لتشغيل اللعبة."
|
||||
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى أحدث اصدار لتشغيل اللعبة."
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "العودة إلى الاستكشاف"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "تثبيت المحتويات الى النموذج"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "تم تفعيل وضع المطوّر."
|
||||
@@ -236,6 +290,75 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "إدارة الموارد"
|
||||
},
|
||||
"app.skins.add-button": {
|
||||
"message": "إضافة مظهر"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "أسحب ثم أفلت"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "تطبيق"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "حذف المظهر"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"message": "سيتم حذف المظهر بشكل نهائي، ولايمكن إرجاعه."
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "هل أنت متأكد من حذف المظهر هذا؟"
|
||||
},
|
||||
"app.skins.edit-button": {
|
||||
"message": "تعديل المظهر"
|
||||
},
|
||||
"app.skins.modal.add-skin-button": {
|
||||
"message": "إضافة المظهر"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "إضافة المظهر"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "رداء"
|
||||
},
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "رداء"
|
||||
},
|
||||
"app.skins.modal.edit-title": {
|
||||
"message": "تعديل المظهر"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "عدل المظهر أولا للحفظ !"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "بدون رداء"
|
||||
},
|
||||
"app.skins.modal.none-cape-option": {
|
||||
"message": "لا شيء"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "حفظ المظهر"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "حفظ..."
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "أرفع المظهر أولا !"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "تعديل المظهر"
|
||||
},
|
||||
"app.skins.previewing-badge": {
|
||||
"message": "أستعراض"
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "تمهل !"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "الرجاء تسجيل الدخول"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "اختيار المظهر"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "انتهى تنزيل تطبيق Modrinth v{version}. أعد التحميل للتحديث الآن، أو تلقائيًا عند إغلاق تطبيق Modrinth."
|
||||
},
|
||||
@@ -254,9 +377,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "اكتمل التنزيل"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "إعادة تحميل"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "تحديث متاح"
|
||||
},
|
||||
@@ -267,7 +387,7 @@
|
||||
"message": "تم تثبيت الإصدار {version} بنجاح!"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "مثال.مودرنث.جج"
|
||||
"message": "مثال (example.modrinth.gg)"
|
||||
},
|
||||
"app.world.server-modal.select-an-option": {
|
||||
"message": "حدد خيارا"
|
||||
@@ -518,6 +638,9 @@
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "الذاكرة المخصَّصة"
|
||||
},
|
||||
"instance.settings.tabs.java.java-path-placeholder": {
|
||||
"message": "/path/to/java"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "النافذة"
|
||||
},
|
||||
@@ -593,6 +716,9 @@
|
||||
"minecraft-account.add-account": {
|
||||
"message": "إضافة حساب"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "حساب ماينكرافت"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "اختيار الحساب"
|
||||
},
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Stahování Javy {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Stahování aktualizace"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Stažené Soubory"
|
||||
"message": "Stahování"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Skrýt více spuštěných instancí"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Použít jako primární instanci"
|
||||
"message": "Nastavit jako primární instanci"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Žádné spuštěné instance"
|
||||
@@ -20,11 +23,17 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primární instance"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Znovu načíst pro aktualizaci"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Zobrazit další spuštěné instance"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Ukončit instanci"
|
||||
"message": "Zastavit instanci"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Aktualizace"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Zobrazit probíhající stahování"
|
||||
@@ -33,10 +42,10 @@
|
||||
"message": "Zobrazit instanci"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Zobrazit záznamy"
|
||||
"message": "Zobrazit logy"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Umožňuje pokročilé vykreslování, jako jsou efekty rozostření, které mohou způsobit problémy s výkonem bez hardwarového vykreslení."
|
||||
"message": "Povolí pokročilé vykreslování, například efekty rozmazání, které mohou bez hardwarového zrychlení způsobovat problémy s výkonem."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Pokročilé vykreslování"
|
||||
@@ -48,16 +57,16 @@
|
||||
"message": "Barevný motiv"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Změňte stránku, na které se spouštěč otevře."
|
||||
"message": "Změňte stránku, která se otevře po spuštění launcheru."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Domov"
|
||||
"message": "Domů"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Knihovna"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Výchozí úvodní stránka"
|
||||
"message": "Výchozí stránka po spuštění"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Zakáže zobrazení jmenovky nad vaší postavou na stránce skinů."
|
||||
@@ -66,31 +75,34 @@
|
||||
"message": "Skrýt jmenovku"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "V sekci „Vrátit se do hry“ na úvodní stránce jsou uvedeny nejnovější světy."
|
||||
"message": "Zahrne nedávné světy do sekce „Vrátit se do hry“ na domovské stránce."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Skočit zpět do světů"
|
||||
"message": "Rychlý návrat do světů"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimalizovat spouštěč při spuštění procesu Minecraftu."
|
||||
"message": "Minimalizovat launcher při spuštění Minecraftu."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimalizovat spouštěč"
|
||||
"message": "Minimalizovat launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Použít systémový rámeček okna (vyžaduje restart aplikace)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Nativní dekorace"
|
||||
"message": "Systémové dekorace"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Vyberte možnost"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Zobrazuje, kolik času jsi strávil hraním instance."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Ukázat herní čas"
|
||||
"message": "Zobrazit odehraný čas"
|
||||
},
|
||||
"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"
|
||||
@@ -102,19 +114,19 @@
|
||||
"message": "Upozorni mě před instalací neznámých modpacků"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Autořizační servery Minecraftu aktuálně nejsou k dispozici. Zkontrolujte si své připojení k internetu a zkuste to znovu později."
|
||||
"message": "Ověřovací Minecraft servery mohou být právě teď nedostupné. Zkontroluj si své připojení k internetu a zkus to znovu později."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Připojení k autorizačním serverům se nezdařilo"
|
||||
"message": "Nelze se připojit k ověřovacím serverům"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Přidat server do instance"
|
||||
"message": "Přidání serveru do instance"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Přidat do instalace"
|
||||
"message": "Přidat do instance"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Přidat do instalace"
|
||||
"message": "Přidat do instance"
|
||||
},
|
||||
"app.browse.add-to-instance-name": {
|
||||
"message": "Přidat do {instanceName}"
|
||||
@@ -123,22 +135,22 @@
|
||||
"message": "Přidáno"
|
||||
},
|
||||
"app.browse.already-added": {
|
||||
"message": "Už přidáno"
|
||||
"message": "Již přidáno"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Zpět k instanci"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Prozkoumat obsah"
|
||||
"message": "Objevovat obsah"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Prozkoumat servery"
|
||||
"message": "Objevovat servery"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Skrýt již přidané servery"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Balíčky Modifikací"
|
||||
"message": "Modpacky"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Přidání obsahu může narušit kompatibilitu při připojení k serveru. Veškerý přidaný obsah se navíc ztratí při aktualizaci obsahu instance serveru."
|
||||
@@ -146,6 +158,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalování"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"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.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -153,7 +168,7 @@
|
||||
"message": "Probíhá instalace modpacku..."
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Přidej popis modpacku..."
|
||||
"message": "Zadej popis modpacku..."
|
||||
},
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Exportovat"
|
||||
@@ -165,10 +180,10 @@
|
||||
"message": "Zahrnout \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Jméno Modpacku"
|
||||
"message": "Název modpacku"
|
||||
},
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Jméno modpacku"
|
||||
"message": "Název modpacku"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Nastavte, které soubory mají být do tohoto exportu zahrnuty"
|
||||
@@ -180,7 +195,7 @@
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Všechna data z instance budou navždy smazána, včetně světů, nastavení a všeho ostatního."
|
||||
"message": "Všechna data tvé instance budou trvale smazána, včetně světů, konfigurací a veškerého nainstalovaného obsahu."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "Tato akce nemůže být vrácena"
|
||||
@@ -189,19 +204,19 @@
|
||||
"message": "Odstranit instanci"
|
||||
},
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Odstanit instanci"
|
||||
"message": "Odstranění instance"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "Tento modpack je už nainstalovaný v instanci <bold>{instanceName}</bold>. Opravdu ho chceš duplikovat?"
|
||||
"message": "Tento modpack je již nainstalovaný v instanci <bold>{instanceName}</bold>. Opravdu ho chceš duplikovat?"
|
||||
},
|
||||
"app.instance.modpack-already-installed.create": {
|
||||
"message": "Vytvořit"
|
||||
},
|
||||
"app.instance.modpack-already-installed.header": {
|
||||
"message": "Modpack už je nainstalovaný"
|
||||
"message": "Modpack je již nainstalován"
|
||||
},
|
||||
"app.instance.modpack-already-installed.instance": {
|
||||
"message": "Instalace"
|
||||
"message": "Instance"
|
||||
},
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "projekt"
|
||||
@@ -213,7 +228,7 @@
|
||||
"message": "{count} projektů bylo přidáno"
|
||||
},
|
||||
"app.instance.mods.share-text": {
|
||||
"message": "Podívejte se na projekty co používám ve svém modpacku!"
|
||||
"message": "Podívejte se na projekty, které používám ve svém modpacku!"
|
||||
},
|
||||
"app.instance.mods.share-title": {
|
||||
"message": "Sdílení obsahu modpacku"
|
||||
@@ -227,11 +242,14 @@
|
||||
"app.instance.worlds.browse-servers": {
|
||||
"message": "Procházet servery"
|
||||
},
|
||||
"app.instance.worlds.delete-world-description": {
|
||||
"message": "'{name}' bude **navždy smazán** a nebude možné jej obnovit."
|
||||
},
|
||||
"app.instance.worlds.delete-world-title": {
|
||||
"message": "Opravdu si jsi jistý, že chceš navždy smazat tenhle svět?"
|
||||
"message": "Jsi si jistý, že chceš trvale smazat tento svět?"
|
||||
},
|
||||
"app.instance.worlds.filter-modded": {
|
||||
"message": "Módováno"
|
||||
"message": "Módovaný"
|
||||
},
|
||||
"app.instance.worlds.filter-offline": {
|
||||
"message": "Offline"
|
||||
@@ -243,19 +261,22 @@
|
||||
"message": "Vanilla"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-description": {
|
||||
"message": "Přidej nebo procházej servery"
|
||||
"message": "Přidej server nebo začni procházet"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "Žádné servery nebo světy nebyly přidány"
|
||||
},
|
||||
"app.instance.worlds.remove-server-description": {
|
||||
"message": "'{name}' bude odstraněn z tvého seznamu i ze hry a nebude možné jej obnovit."
|
||||
},
|
||||
"app.instance.worlds.remove-server-description-with-address": {
|
||||
"message": "'{name}' ({address}) bude odstraněn z vašeho seznamu, a to i ve hře, a nebude možné jej obnovit."
|
||||
"message": "'{name}' ({address}) bude odstraněn z tvého seznamu i ze hry a nebude možné jej obnovit."
|
||||
},
|
||||
"app.instance.worlds.remove-server-title": {
|
||||
"message": "Opravdu chceš odstranit {name}?"
|
||||
},
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "Hledat ve světech {count}..."
|
||||
"message": "Hledat v {count} světech..."
|
||||
},
|
||||
"app.instance.worlds.this-server": {
|
||||
"message": "tento server"
|
||||
@@ -264,19 +285,19 @@
|
||||
"message": "Požadovaný obsah"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "Nainstaluj ke hraní"
|
||||
"message": "Nainstaluj pro hraní"
|
||||
},
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Instalovat"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural,one {#mód} other {#módy}}"
|
||||
"message": "{count, plural, one {# mód} few {# módy} other {# módů}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Povinný modpack"
|
||||
"message": "Požadovaný modpack"
|
||||
},
|
||||
"app.modal.install-to-play.server-requires-mods": {
|
||||
"message": "Tento server k hraní vyžaduje módy. Klikni na instalovat pro získání potřebných módů z Modrinth."
|
||||
"message": "Tento server k hraní vyžaduje módy. Klikni na instalovat pro získání potřebných módů z Modrinth a poté se rovnou připoj na server."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Sdílená instance"
|
||||
@@ -288,22 +309,22 @@
|
||||
"message": "Zobrazit obsah"
|
||||
},
|
||||
"app.modal.update-to-play.header": {
|
||||
"message": "Updatuj ke hraní"
|
||||
"message": "Aktualizuj pro hraní"
|
||||
},
|
||||
"app.modal.update-to-play.update-required": {
|
||||
"message": "Je vyžadována aktualizace"
|
||||
},
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Je nutná aktualizace ke hraní {name}. Prosím updatuj na nejnovější verzi k spuštění hry."
|
||||
"message": "Pro hraní {name} je vyžadována aktualizace. Prosím aktualizuj na nejnovější verzi, abys mohl hru spustit."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Tento projekt je již nainstalován"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Zpět na Prozkoumat"
|
||||
"message": "Zpět k Objevování"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Nainstalovat obsah do instnce"
|
||||
"message": "Nainstalovat obsah do instance"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Vývojářský režim povolen."
|
||||
@@ -332,21 +353,105 @@
|
||||
"app.skins.add-button": {
|
||||
"message": "Přidat skin"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "Přetáhni a pusť"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Použít"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "Smazat skin"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"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?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Nepodařilo se načíst přetažený soubor."
|
||||
},
|
||||
"app.skins.dropped-file-error.title": {
|
||||
"message": "Chyba při načítání souboru"
|
||||
},
|
||||
"app.skins.edit-button": {
|
||||
"message": "Upravit skin"
|
||||
},
|
||||
"app.skins.modal.add-skin-button": {
|
||||
"message": "Přidat skin"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "Přidání skinu"
|
||||
},
|
||||
"app.skins.modal.arm-style-section": {
|
||||
"message": "Typ paží"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "Štíhlý"
|
||||
},
|
||||
"app.skins.modal.arm-style-wide": {
|
||||
"message": "Široký"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "Plášť"
|
||||
},
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "Plášť"
|
||||
},
|
||||
"app.skins.modal.edit-title": {
|
||||
"message": "Úprava skinu"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "Nejdříve uprav skin!"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "Bez pláště"
|
||||
},
|
||||
"app.skins.modal.none-cape-option": {
|
||||
"message": "Žádný"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "Nahradit texturu"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "Uložit skin"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "Ukládání..."
|
||||
},
|
||||
"app.skins.modal.texture-section": {
|
||||
"message": "Textura"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "Nejdříve nahraj skin!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "Upravit skin"
|
||||
},
|
||||
"app.skins.previewing-badge": {
|
||||
"message": "Náhled"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "Měníš svůj skin příliš často. Servery Mojangu dočasně zablokovaly další požadavky. Počkej chvíli, než to zkusíš znovu."
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Zpomal!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Tvé pořadí skinů se nepodařilo uložit."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Nepodařilo se změnit pořadí skinů"
|
||||
},
|
||||
"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": "Výchozí skiny"
|
||||
},
|
||||
"app.skins.section.minecon-earth-2017": {
|
||||
"message": "MINECON Earth 2017"
|
||||
},
|
||||
@@ -356,14 +461,47 @@
|
||||
"app.skins.section.modrinth-pride": {
|
||||
"message": "Modrinth Pride"
|
||||
},
|
||||
"app.skins.section.modrinth-pride.tooltip": {
|
||||
"message": "Tyto skiny jsi obdržel za přispění do sbírky Modrinth Pride behěm Pride měsíce."
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "Mounts of Mayhem"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "Uložené skiny"
|
||||
},
|
||||
"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": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Přihlásit se"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "Přihlas se prosím ke svému Minecraft účtu, abys mohl/a využívat funkce pro správu skinů v aplikaci Modrinth."
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "Nadšený Modrinth Bot"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "Přihlas se prosím"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Výběr skinu"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Stahování aplikace Modrinth v{version} bylo dokončeno. Nainstalujte aktualizaci nyní nebo automaticky po zavření aplikace Modrinth."
|
||||
"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": "Aplikace Modrinth v{version} je k dispozici. Aktualizujte 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": "Aplikace Modrinth v{version} je nyní k dispozici! Protože jste v měřené síti, nebyla stažena automaticky."
|
||||
@@ -378,10 +516,10 @@
|
||||
"message": "Stahování bylo dokončeno"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Načíst znovu"
|
||||
"message": "Znovu načíst pro aktualizaci"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Je k dispozici aktualizace"
|
||||
"message": "Aktualizace k dispozici"
|
||||
},
|
||||
"app.update.complete-toast.text": {
|
||||
"message": "Kliknutím sem zobrazíte seznam změn."
|
||||
@@ -393,13 +531,13 @@
|
||||
"message": "priklad.modrinth.gg"
|
||||
},
|
||||
"app.world.server-modal.select-an-option": {
|
||||
"message": "Vyberte možnost"
|
||||
"message": "Vybrat možnost"
|
||||
},
|
||||
"app.world.world-item.incompatible-version": {
|
||||
"message": "Nekompatibilní verze {version}"
|
||||
},
|
||||
"app.world.world-item.not-played-yet": {
|
||||
"message": "Ještě nehráno"
|
||||
"message": "Zatím nehráno"
|
||||
},
|
||||
"app.world.world-item.offline": {
|
||||
"message": "Offline"
|
||||
@@ -411,25 +549,25 @@
|
||||
"message": "Přidat přítele"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} přátelé {count, plural, one {request} other {requests}}"
|
||||
"message": "{count} {count, plural, one {žádost} few {žádosti} other {žádostí}} o přátelství"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Poslat žádost o přátelství"
|
||||
},
|
||||
"friends.add-friend.title": {
|
||||
"message": "Přidávání kamaráda"
|
||||
"message": "Přidání přítele"
|
||||
},
|
||||
"friends.add-friend.username.description": {
|
||||
"message": "Může být jiné než jejich Minecraft jméno!"
|
||||
"message": "Může se lišit od jejich Minecraft přezdívky!"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "Zadejte uživatelské jméno Modrinth..."
|
||||
"message": "Zadej přezdívku na Modrinthu..."
|
||||
},
|
||||
"friends.add-friend.username.title": {
|
||||
"message": "Jak se váš kamarád jmenuje na Modrinthu?"
|
||||
"message": "Jaká je přezdívka tvého přítele na Modrinthu?"
|
||||
},
|
||||
"friends.add-friends-to-share": {
|
||||
"message": "<link>Přidejte si přátelé</link> a podívejte se, co hrají!"
|
||||
"message": "<link>Přidej si přátele</link>, abys viděl, co hrají!"
|
||||
},
|
||||
"friends.friend.cancel-request": {
|
||||
"message": "Zrušit žádost"
|
||||
@@ -444,7 +582,7 @@
|
||||
"message": "Zobrazit profil"
|
||||
},
|
||||
"friends.heading": {
|
||||
"message": "Kamarádi"
|
||||
"message": "Přátelé"
|
||||
},
|
||||
"friends.heading.active": {
|
||||
"message": "Aktivní"
|
||||
@@ -458,6 +596,9 @@
|
||||
"friends.heading.pending": {
|
||||
"message": "Čeká na vyřízení"
|
||||
},
|
||||
"friends.no-friends-match": {
|
||||
"message": "Žádní přátelé neodpovídají ''{query}''"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "Hledat přátele..."
|
||||
},
|
||||
@@ -465,7 +606,7 @@
|
||||
"message": "{title} - {count}"
|
||||
},
|
||||
"friends.sign-in-to-add-friends": {
|
||||
"message": "<link>Přihlašte se ke svému Modrinth účtu</link>, abyste jsi přidali přátele a uviděli co hrají!"
|
||||
"message": "<link>Přihlas se k Modrinth účtu</link>, abys mohl přidávat přátele a vidět, co hrají!"
|
||||
},
|
||||
"instance.add-server.add-and-play": {
|
||||
"message": "Přidat a hrát"
|
||||
@@ -477,10 +618,10 @@
|
||||
"message": "Vypnuto"
|
||||
},
|
||||
"instance.add-server.resource-pack.enabled": {
|
||||
"message": "Zapnuto"
|
||||
"message": "Povolený"
|
||||
},
|
||||
"instance.add-server.resource-pack.prompt": {
|
||||
"message": "Ptát se"
|
||||
"message": "Zeptat se"
|
||||
},
|
||||
"instance.add-server.title": {
|
||||
"message": "Přidat server"
|
||||
@@ -489,7 +630,7 @@
|
||||
"message": "Upravit server"
|
||||
},
|
||||
"instance.edit-world.hide-from-home": {
|
||||
"message": "Skrýt z domovské obrazovky"
|
||||
"message": "Skrýt z domovské stránky"
|
||||
},
|
||||
"instance.edit-world.name": {
|
||||
"message": "Název"
|
||||
@@ -498,7 +639,7 @@
|
||||
"message": "Minecraft svět"
|
||||
},
|
||||
"instance.edit-world.reset-icon": {
|
||||
"message": "Resetovat ikonu"
|
||||
"message": "Obnovit ikonu"
|
||||
},
|
||||
"instance.edit-world.title": {
|
||||
"message": "Upravit svět"
|
||||
@@ -516,22 +657,22 @@
|
||||
"message": "Název"
|
||||
},
|
||||
"instance.server-modal.placeholder-name": {
|
||||
"message": "Minecraft Server"
|
||||
"message": "Minecraft server"
|
||||
},
|
||||
"instance.server-modal.resource-pack": {
|
||||
"message": "Balíčky textur"
|
||||
"message": "Balíček textur"
|
||||
},
|
||||
"instance.settings.tabs.general": {
|
||||
"message": "Obecné"
|
||||
},
|
||||
"instance.settings.tabs.general.delete": {
|
||||
"message": "Odstranit instalaci"
|
||||
"message": "Odstranění instance"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.button": {
|
||||
"message": "Odstranit instalaci"
|
||||
"message": "Odstranit instanci"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.description": {
|
||||
"message": "Trvale smaže instalaci z vašeho zařízení, včetně vašich světů, konfigurací a veškerého nainstalovaného obsahu. Buďte opatrní, protože jakmile instalaci smažete, nebude možné ji obnovit."
|
||||
"message": "Trvale smaže instanci z tvého zařízení, včetně světů, konfigurací a veškerého nainstalovaného obsahu. Buď opatrný, jakmile instanci smažeš, nepůjde ji obnovit."
|
||||
},
|
||||
"instance.settings.tabs.general.deleting.button": {
|
||||
"message": "Mazání..."
|
||||
@@ -540,40 +681,64 @@
|
||||
"message": "Duplikovat"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-button.tooltip.installing": {
|
||||
"message": "Nelze duplikovat při instalaci."
|
||||
"message": "Nelze duplikovat během instalace."
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance": {
|
||||
"message": "Duplikovat instalaci"
|
||||
"message": "Duplikace instance"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance.description": {
|
||||
"message": "Vytvoří kopii této instalace, včetně světů, konfigurací, modů atd."
|
||||
"message": "Vytvoří kopii této instance, včetně světů, konfigurací, modů, atd."
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "Upravit ikonu"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.remove": {
|
||||
"message": "Odstranit ikonu"
|
||||
"message": "Odebrat ikonu"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.replace": {
|
||||
"message": "Změnit ikonu"
|
||||
"message": "Nahradit ikonu"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.select": {
|
||||
"message": "Vybrat ikonu"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups": {
|
||||
"message": "Skupina knihoven"
|
||||
"message": "Skupiny knihovny"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.create": {
|
||||
"message": "Vytvořit novou skupinu"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.description": {
|
||||
"message": "Skupiny knihoven vám umožňují uspořádat instalace do různých sekcí."
|
||||
"message": "Skupiny knihovny ti umožňují uspořádat instance do různých sekcí v knihovně."
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.enter-name": {
|
||||
"message": "Zadejte název skupiny"
|
||||
"message": "Zadej název skupiny"
|
||||
},
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Název"
|
||||
"message": "Jméno"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Aktualizační kanál"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Vydané, beta a alfa verze se zobrazí jako dostupné aktualizace."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Vydané a beta verze se zobrazí jako dostupné aktualizace."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Vydání"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Pouze vydané verze se zobrazí jako dostupné aktualizace."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Vyber aktualizační kanál"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Spouštěcí hooky"
|
||||
@@ -582,28 +747,28 @@
|
||||
"message": "Vlastní spouštěcí hooky"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "Hooky umožňují pokročilým uživatelům spouštět určité systémové příkazy před a po spuštění hry."
|
||||
"message": "Hooky umožňují pokročilým uživatelům spustit určité systémové příkazy před a po spuštění hry."
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit": {
|
||||
"message": "Po ukončení"
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.description": {
|
||||
"message": "Spušteno po vypnutí hry."
|
||||
"message": "Provede se po ukončení hry."
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.enter": {
|
||||
"message": "Zadejte příkaz k provedení po ukončení..."
|
||||
"message": "Zadej příkaz k provedení po ukončení..."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch": {
|
||||
"message": "Před spuštěním"
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.description": {
|
||||
"message": "Spouštějí se před spuštěním instalace."
|
||||
"message": "Provede se před spuštěním instance."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.enter": {
|
||||
"message": "Zadejte příkaz k provedení před spuštěním..."
|
||||
"message": "Zadej příkaz k provedení před spuštěním..."
|
||||
},
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Spouštěcí hooky hry"
|
||||
"message": "Spouštěcí příkazy hry"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Wrapper"
|
||||
@@ -612,7 +777,7 @@
|
||||
"message": "Příkaz wrapperu pro spuštění Minecraftu."
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.enter": {
|
||||
"message": "Zadejte příkaz wrapperu..."
|
||||
"message": "Zadej příkaz wrapperu..."
|
||||
},
|
||||
"instance.settings.tabs.installation": {
|
||||
"message": "Instalace"
|
||||
@@ -623,6 +788,24 @@
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java a pamět"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-environment-variables": {
|
||||
"message": "Vlastní proměnná prostředí"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-arguments": {
|
||||
"message": "Vlastní argumenty Javy"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-installation": {
|
||||
"message": "Vlastní instalace Javy"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-memory-allocation": {
|
||||
"message": "Vlastní nastavení paměti"
|
||||
},
|
||||
"instance.settings.tabs.java.enter-environment-variables": {
|
||||
"message": "Zadej proměnná prostředí..."
|
||||
},
|
||||
"instance.settings.tabs.java.enter-java-arguments": {
|
||||
"message": "Zadej argumenty Javy..."
|
||||
},
|
||||
"instance.settings.tabs.java.environment-variables": {
|
||||
"message": "Proměnné prostředí"
|
||||
},
|
||||
@@ -630,13 +813,16 @@
|
||||
"message": "Hooky"
|
||||
},
|
||||
"instance.settings.tabs.java.java-arguments": {
|
||||
"message": "Java argumenty"
|
||||
"message": "Argumenty Javy"
|
||||
},
|
||||
"instance.settings.tabs.java.java-installation": {
|
||||
"message": "Instalace Javy"
|
||||
},
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "Paměti přiřazeno"
|
||||
"message": "Přiřazeno paměti"
|
||||
},
|
||||
"instance.settings.tabs.java.java-path-placeholder": {
|
||||
"message": "/path/to/java"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "Okno"
|
||||
@@ -645,10 +831,10 @@
|
||||
"message": "Vlastní nastavení okna"
|
||||
},
|
||||
"instance.settings.tabs.window.fullscreen": {
|
||||
"message": "Celá obrazovka"
|
||||
"message": "Režim celé obrazovky"
|
||||
},
|
||||
"instance.settings.tabs.window.fullscreen.description": {
|
||||
"message": "Spustit hru v plné obrazovce pokud spuštěno (použitím options.txt)."
|
||||
"message": "Při spuštění otevřít hru v režimu celé obrazovky (použitím options.txt)."
|
||||
},
|
||||
"instance.settings.tabs.window.height": {
|
||||
"message": "Výška"
|
||||
@@ -657,7 +843,7 @@
|
||||
"message": "Výška herního okna při spuštění."
|
||||
},
|
||||
"instance.settings.tabs.window.height.enter": {
|
||||
"message": "Zadejte výšku..."
|
||||
"message": "Zadej výšku..."
|
||||
},
|
||||
"instance.settings.tabs.window.width": {
|
||||
"message": "Šířka"
|
||||
@@ -666,19 +852,19 @@
|
||||
"message": "Šířka herního okna při spuštění."
|
||||
},
|
||||
"instance.settings.tabs.window.width.enter": {
|
||||
"message": "Zadejte šířku..."
|
||||
"message": "Zadej šířku..."
|
||||
},
|
||||
"instance.worlds.a_minecraft_server": {
|
||||
"message": "Minecraft Server"
|
||||
"message": "Minecraft server"
|
||||
},
|
||||
"instance.worlds.cant_connect": {
|
||||
"message": "Nelze připojit k serveru"
|
||||
"message": "Nelze se připojit k serveru"
|
||||
},
|
||||
"instance.worlds.copy_address": {
|
||||
"message": "Kopírovat adresu"
|
||||
},
|
||||
"instance.worlds.dont_show_on_home": {
|
||||
"message": "Nezobrazuje na domovské stránce"
|
||||
"message": "Nezobrazovat na domovské stránce"
|
||||
},
|
||||
"instance.worlds.game_already_open": {
|
||||
"message": "Instance je již otevřená"
|
||||
@@ -693,13 +879,13 @@
|
||||
"message": "Spravováno projektem serveru"
|
||||
},
|
||||
"instance.worlds.no_contact": {
|
||||
"message": "Server se nepodařilo spojit"
|
||||
"message": "Se serverem se nepodařilo spojit"
|
||||
},
|
||||
"instance.worlds.no_server_quick_play": {
|
||||
"message": "Můžeš rovnou skočit na server pouze v Minecraftu Alpha 1.0.5+"
|
||||
"message": "Připojit se rovnou k serverům můžeš pouze s Minecraft verzí Alpha 1.0.5+"
|
||||
},
|
||||
"instance.worlds.no_singleplayer_quick_play": {
|
||||
"message": "V Minecraftu 1.20+ se dá rovnou přeskočit pouze do singleplayerových světů"
|
||||
"message": "Můžeš se rovnou připojit do světů pro jednoho hráče pouze s Minecraft verzí 1.20+"
|
||||
},
|
||||
"instance.worlds.play_instance": {
|
||||
"message": "Hrát instanci"
|
||||
@@ -708,22 +894,22 @@
|
||||
"message": "Zobrazit instanci"
|
||||
},
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Svět je použit"
|
||||
"message": "Svět je používán"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Přidat účet"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Účet Minecraft"
|
||||
"message": "Minecraft účet"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Nejste přihlášeni"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Odstranit účet"
|
||||
"message": "Odebrat účet"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Vyberte účet"
|
||||
"message": "Zvolte účet"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Přihlásit se do Minecraftu"
|
||||
@@ -732,31 +918,31 @@
|
||||
"message": "Poskytováno instancí"
|
||||
},
|
||||
"search.filter.locked.instance-game-version.title": {
|
||||
"message": "Verze hry je poskytnut instanci"
|
||||
"message": "Verze hry je poskytována instancí"
|
||||
},
|
||||
"search.filter.locked.instance-loader.title": {
|
||||
"message": "Spouštěč je poskytnut instancí"
|
||||
"message": "Načítací modul je poskytován instancí"
|
||||
},
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchronizováno z instancí"
|
||||
"message": "Synchronizováno s instancí"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Poskytováno serverem"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Jen klientské mody mohou být přidány na server"
|
||||
"message": "Pouze módy ze strany klientu mohou být přidány na server"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Verzi hry poskytuje server"
|
||||
"message": "Verze hry je poskytována serverem"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader zprostředkovává server"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Soubor je zkontrolován pouze v případě, že je nahrán na Modrinth, a to bez ohledu na jeho formát (včetně formátu .mrpack)."
|
||||
"message": "Soubor je zkontrolován pouze v případě, že je nahrán na Modrinth, bez ohledu na jeho formát (včetně formátu .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Toto varování již nezobrazovat"
|
||||
"message": "Toto varování znovu nezobrazovat"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Potvrdit instalaci"
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Downloader Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Downloader opdatering"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primære instance"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Genindlæs for at opdater"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Vis flere kørende instances"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Stop instance"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Opdater"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Vis aktive downloads"
|
||||
},
|
||||
@@ -143,6 +152,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installere"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Ingen tilgængelige versioner passer til {compatibilityLabel}. Vælg en version for at installere. Afhængigheder bliver ikke installeret automatisk."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -473,9 +485,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "Download færdiggjort"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Geninlæs"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Opdatering tilgængelig"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} wird heruntergeladen"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Update wird heruntergeladen"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primäre Instanz"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Zum Aktualisieren neu laden"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Weitere laufende Instanzen anzeigen"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Instanz stoppen"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Aktualisieren"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Aktive Downloads anzeigen"
|
||||
},
|
||||
@@ -419,6 +428,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Nicht so schnell!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Deine Skin-Reihenfolge konnte nicht gespeichert werden."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Skins konnten nicht neu angeordnet werden"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -492,7 +507,7 @@
|
||||
"message": "Download abgeschlossen"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Neu Laden"
|
||||
"message": "Zum Aktualisieren neu laden"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Aktualisierung verfügbar"
|
||||
@@ -692,6 +707,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Name"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Update-Kanal"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Release-, Beta- und Alpha-Versionen werden als verfügbare Updates angezeigt."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Release- und Beta-Versionen werden als verfügbare Updates angezeigt."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Release"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Nur Release-Versionen werden als verfügbare Updates angezeigt."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Update-Kanal auswählen"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Start Hooks"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} wird heruntergeladen"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Update wird heruntergeladen"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
@@ -12,7 +15,7 @@
|
||||
"message": "Zur primären Instanz machen"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Keine aktiven Instanzen"
|
||||
"message": "Keine laufenden Instanzen"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primäre Instanz"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Zum Aktualisieren neu laden"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Weitere laufende Instanzen anzeigen"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Instanz stoppen"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Aktualisieren"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Aktive Downloads anzeigen"
|
||||
},
|
||||
@@ -33,7 +42,7 @@
|
||||
"message": "Instanz anzeigen"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Protokolle anzeigen"
|
||||
"message": "Logs anzeigen"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Aktiviert erweiterte Rendering-Funktionen wie Unschärfe-Effekte, welche ohne Hardware-beschleunigtes Rendering zu Leistungsproblemen führen können."
|
||||
@@ -414,11 +423,17 @@
|
||||
"message": "Vorschau"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "Du wechselst dein Skin zu oft. Die Server von Mojang haben weitere Anfragen vorübergehend blockiert. Bitte warte einen Moment, bevor du es erneut versuchst."
|
||||
"message": "Du wechselst deinen Skin zu oft. Die Server von Mojang haben weitere Anfragen vorübergehend blockiert. Bitte warte einen Moment, bevor du es erneut versuchst."
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Langsamer!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Die Anordnung deiner Skins konnte nicht gespeichert werden."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Skins konnten nicht neu angeordnet werden"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -492,7 +507,7 @@
|
||||
"message": "Download abgeschlossen"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Neu laden"
|
||||
"message": "Zum Aktualisieren neu laden"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Aktualisierung verfügbar"
|
||||
@@ -692,6 +707,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Name"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Aktualisierungskanal"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Voll, Beta- und Alpha-Versionen werden als verfügbare Aktualisierungen angezeigt."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Voll- und Beta-Versionen werden als verfügbare Aktualisierungen angezeigt."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Vollversion"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Nur Vollversionen werden als verfügbare Aktualisierungen angezeigt."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Aktualisierungskanal auswählen"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Startargumente"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Descargando Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Descargando actualización"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Descargas"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instancia principal"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Reinicia para actualizar"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostrar más instancias en ejecución"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Detener instancia"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Actualizar"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Ver descargas en curso"
|
||||
},
|
||||
@@ -303,7 +312,7 @@
|
||||
"message": "Este proyecto ya está instalado"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Volver a descubrir contenido"
|
||||
"message": "Volver al explorador"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instalar contenido a la instancia"
|
||||
@@ -336,7 +345,7 @@
|
||||
"message": "Añadir skin"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "Arrastrar y soltar"
|
||||
"message": "Arrastra y suelta"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Aplicar"
|
||||
@@ -345,10 +354,10 @@
|
||||
"message": "Eliminar skin"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"message": "Esto eliminará permanentemente el skin seleccionado. Esta acción no se puede deshacer."
|
||||
"message": "Esto eliminará permanentemente la skin seleccionada. Esta acción no se puede deshacer."
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "¿Estás seguro de que quieres eliminar este skin?"
|
||||
"message": "¿Estás seguro de que quieres eliminar esta skin?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Error al leer el archivo arrastrado."
|
||||
@@ -363,7 +372,7 @@
|
||||
"message": "Añadir skin"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "Añadiendo un skin"
|
||||
"message": "Añadiendo una skin"
|
||||
},
|
||||
"app.skins.modal.arm-style-section": {
|
||||
"message": "Estilo de brazo"
|
||||
@@ -405,7 +414,7 @@
|
||||
"message": "Textura"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "¡Primero carga un skin!"
|
||||
"message": "¡Primero carga una skin!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "Editar skin"
|
||||
@@ -419,6 +428,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "¡Más despacio!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "No se ha podido guardar el orden de las skins."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Error al reordenar las skins"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Constructores y Biomas"
|
||||
},
|
||||
@@ -444,7 +459,7 @@
|
||||
"message": "Monturas del Caos"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "Skins guardados"
|
||||
"message": "Skins guardadas"
|
||||
},
|
||||
"app.skins.section.striding-hero": {
|
||||
"message": "Striding Hero"
|
||||
@@ -492,7 +507,7 @@
|
||||
"message": "Descarga completada"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Recargar"
|
||||
"message": "Reinicia para actualizar"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Actualización disponible"
|
||||
@@ -692,6 +707,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nombre"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Canal de actualizaciones"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Las versiones estables, beta y alpha se mostrarán como actualizaciones disponibles. "
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Las versiones estables y beta serán mostradas como actualizaciones disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Estable"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Sólo las versiones estables serán mostradas como actualizaciones disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Selecciona el canal de actualizaciones"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Hooks de inicio"
|
||||
},
|
||||
@@ -882,7 +921,7 @@
|
||||
"message": "Proporcionado por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Solo se puede añadir mods que sean del lado del cliente a la instancia del servidor"
|
||||
"message": "Solo se pueden añadir mods que sean del lado del cliente a la instancia del servidor"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "La versión del juego es proporcionada por el servidor"
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Descargando Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Descargando actualización"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Descargas"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instancia principal"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Recargar para actualizar"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostrar más instancias en ejecución"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Detener instancia"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Actualizar"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Mostrar descargas activas"
|
||||
},
|
||||
@@ -48,7 +57,7 @@
|
||||
"message": "Tema de color"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Cambia la página que se abre al iniciar el lanzador."
|
||||
"message": "Cambia la página en la que el launcher inicia."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Inicio"
|
||||
@@ -428,6 +437,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "¡Cálmate!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "No se ha podido guardar el orden de las skins."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Error al reordenar las skins"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders y Biomas"
|
||||
},
|
||||
@@ -501,7 +516,7 @@
|
||||
"message": "Descarga completada"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Recargar"
|
||||
"message": "Recargar para actualizar"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Actualización disponible"
|
||||
@@ -701,6 +716,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nombre"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Canal de actualizaciones"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Se mostrarán las versiones estables, beta y alfa como actualizaciones disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Se mostrarán las versiones estables y beta como actualizaciones disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Estable"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Solo se mostrarán las versiones estables como actualizaciones disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Seleccionar canal de actualizaciones"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Hooks de lanzamiento"
|
||||
},
|
||||
@@ -882,7 +921,7 @@
|
||||
"message": "Versión del juego proporciona por la instancia"
|
||||
},
|
||||
"search.filter.locked.instance-loader.title": {
|
||||
"message": "Cargador proporcionado por la instancia"
|
||||
"message": "Loader proporcionado por la instancia"
|
||||
},
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar con la instancia"
|
||||
@@ -897,7 +936,7 @@
|
||||
"message": "La versión del juego es proporcionada por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "El cargador lo proporciona el servidor"
|
||||
"message": "Loader proporcionado por el servidor"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Un archivo solo es revisado si es subido a Modrinth, independientemente de su formato de archivo (incluyendo .mrpack)."
|
||||
|
||||
@@ -143,6 +143,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Asennetaan"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Yhtään versiota ei ole saatavilla yhteensopivuudella {compatibilityLabel}. Valitse asennettava versio siitä huolimatta. Riippuvuuksia ei asenneta automaattisesti."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -332,6 +335,21 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "Resurssien hallinta"
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Skinien järjestyksen muuttaminen epäonnistui"
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "Sekasorron ratsut"
|
||||
},
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Pienten vallankumous"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "Kirjaudu sisään"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Skinivalinta"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App versio {version} on ladattu. Voit käynnistää sovelluksen uudelleen päivittääksesi heti tai antaa päivityksen asentua automaattisesti, kun suljet Modrinth Appin."
|
||||
},
|
||||
@@ -351,7 +369,7 @@
|
||||
"message": "Lataus valmis"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Lataa uudelleen"
|
||||
"message": "Lataa uudelleen päivittääksesi"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Päivitys saatavilla"
|
||||
@@ -551,6 +569,15 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nimi"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Julkaisu"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Aloitustoiminnot"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Dina-download ang Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Dina-download ang update"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Mga downloads"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Pangunahing instansiya"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Mag-reload upang ma-update"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Ipakita ang mas marami pang tumatakbong instansiya"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Itigil ang instansiya"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Mag-update"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Tignan ang mga active downloads"
|
||||
},
|
||||
@@ -86,6 +95,9 @@
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Pumili ng opsiyon"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Magpapakita ang katagal ng iyong paglalaro sa isang instansiya."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Pagpapagana na mata-toggle ang sidebar."
|
||||
},
|
||||
@@ -377,9 +389,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "Nakumpleto ang pagdownload"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Mag-reload"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "May bagong update"
|
||||
},
|
||||
@@ -578,6 +587,12 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Pangalan"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Mga launch hook"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Téléchargement de Java {version} en cours"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Téléchargement de la mise à jour"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Téléchargements"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instance première"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Recharger pour mettre à jour"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Montrer plus d'instances en exécution"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Arrêter l'instance"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Mettre à jour"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Voir les téléchargements actifs"
|
||||
},
|
||||
@@ -93,7 +102,7 @@
|
||||
"message": "Afficher le temps de jeu"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Permet d'ouvrir de fermer la barre latérale."
|
||||
"message": "Permet d'ouvrir ou de fermer la barre latérale."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Tiroir latéral"
|
||||
@@ -419,6 +428,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Du calme !"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "L'ordre des skins n'a pas pu être sauvegardé."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Impossible de réorganiser les skins"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -492,7 +507,7 @@
|
||||
"message": "Téléchargement terminé"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Recharger"
|
||||
"message": "Recharger pour mettre à jour"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Mise à jour disponible"
|
||||
@@ -689,6 +704,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nom"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Canal de mise à jour"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Les versions stables, bêta et alpha seront affichées comme mises à jour disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Bêta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Les versions stables et bêta seront affichées comme mises à jour disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Stable"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Seules les versions stables seront affichées comme mises à jour disponibles."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Sélectionner le canal de mise à jour"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Crochets de lancement"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "מוריד את Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "מוריד עדכון"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "הורדות"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "החבא יותר התקנים רצים"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "הפוך למקרה ראשי"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "לא מקוון"
|
||||
},
|
||||
@@ -11,6 +23,9 @@
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "מאפשר עיבוד מתקדם כגון אפקטים טשטוש העלולים לגרום לבעיות ביצועים ללא עיבוד מואץ לחומרה."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "חזרה לעולמות"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "ייתכן ששרתי האימות של Minecraft מושבתים כרגע. יש לבדוק את חיבור האינטרנט שלך ולנסות שוב מאוחר יותר."
|
||||
},
|
||||
@@ -221,9 +236,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "הורדה הושלמה"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "רענן"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "עדכון זמין"
|
||||
},
|
||||
@@ -254,6 +266,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "הוספת חבר"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count, plural, one {בקשת חברות אחת} other {# בקשות חברות}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "שליחת בקשת חברות"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "A Java {version} letöltése"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Frissítés letöltése"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Letöltések"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Elsődleges példány"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Töltsd újra a frissítéshez"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "További futó pédányok megjelenítése"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Példány leállítása"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Frissítés"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Aktív letöltések megtekintése"
|
||||
},
|
||||
@@ -69,7 +78,7 @@
|
||||
"message": "A kezdőlap „Folytasd itt” részében a legújabb világok is megtalálhatók."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "„Folytasd itt” a világokat is"
|
||||
"message": "Visszaugrás a világokba"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimalizáld az indítót a Minecraft indításakor."
|
||||
@@ -281,6 +290,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Telepítés"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count} mod"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Szükséges modcsomag"
|
||||
},
|
||||
@@ -498,7 +510,7 @@
|
||||
"message": "Sikeres letöltés"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Frissítés"
|
||||
"message": "Töltsd újra a frissítéshez"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Frissítés elérhető"
|
||||
@@ -530,6 +542,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Barát hozzáadása"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} barát {count, plural, other {kérelem}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Barátkérelem elküldése"
|
||||
},
|
||||
@@ -695,6 +710,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Név"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Frissítési csatorna"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "A kiadás, béta és az alfa verziók is meg fognak jelenni az elérhető frissítések között."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Béta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "A kiadás és a béta verziók is meg fognak jelenni az elérhető frissítések között."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Kiadás"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Csak a release verziók fognak megjelenni az elérhető frissítések között."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Válaszd ki a frissítési csatornát"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Indítási horgok"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Mengunduh Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Mengunduh pembaruan"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Unduhan"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instans utama"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Muat ulang untuk memperbarui"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Tampilkan lebih banyak instans yang sedang berjalan"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Hentikan instans"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Perbarui"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Lihat pengunduhan berlangsung"
|
||||
},
|
||||
@@ -86,6 +95,12 @@
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Pilih opsi"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Menampilkan seberapa lama Anda memainkan sebuah instans."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Tampilkan waktu bermain"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Menghidupkan kemampuan untuk menghidupkan dan mematikan bilah sisi."
|
||||
},
|
||||
@@ -143,6 +158,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Memasang"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Tidak ada versi tersedia yang cocok untuk {compatibilityLabel}. Anda tetap dapat memilih versi yang akan dipasang. Dependensi tidak akan dipasang secara otomatis."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -272,6 +290,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Pasang"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# mod}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Paket mod yang diperlukan"
|
||||
},
|
||||
@@ -416,6 +437,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Pelan-pelan!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Urutan rupa Anda tidak dapat disimpan."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Gagal mengurut ulang rupa"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -489,7 +516,7 @@
|
||||
"message": "Selesai mengunduh"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Muat ulang"
|
||||
"message": "Muat ulang untuk memperbarui"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Pembaruan tersedia"
|
||||
@@ -521,6 +548,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Tambah teman"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, other {permintaan}} teman"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Kirim permintaan teman"
|
||||
},
|
||||
@@ -686,6 +716,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nama"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Perbarui kanal"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Versi rilisan, beta, dan alpha akan ditampilkan sebagai pembaruan yang tersedia."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Versi beta dan alpha akan ditampilkan sebagai pembaruan yang tersedia."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Rilisan"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Hanya versi rilisan yang akan ditampilkan sebagai pembaruan yang tersedia."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Pilih kanal pembaruan"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Luncurkan kait"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Scaricando l'aggiornamento"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Download"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primaria"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Ricarica per aggiornare"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostra più istanze in esecuzione"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Ferma l'istanza"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Aggiorna"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Mostra i download attivi"
|
||||
},
|
||||
@@ -90,7 +99,7 @@
|
||||
"message": "Mostra quanto tempo hai speso giocando a un'istanza."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Mostra tempo di gioco"
|
||||
"message": "Mostra le ore di gioco"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Mostra o nascondi la barra laterale."
|
||||
@@ -144,13 +153,13 @@
|
||||
"message": "Pacchetti di mod"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Aggiungere dei contenuti potrebbe causare problemi entrando nel server. Inoltre, qualsiasi contenuto aggiunto sarà perso a ogni aggiornamento dei contenuti dell'istanza."
|
||||
"message": "Aggiungere dei contenuti potrebbe causare problemi entrando nel server. Inoltre, qualsiasi contenuto aggiunto sarà perso quando aggiorni i contenuti dell'istanza."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installazione"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Nessuna versione disponibile corrisponde a {compatibilityLabel}. Seleziona una versione da installare comunque. Le dipendenze non verranno scaricate automaticamente."
|
||||
"message": "Nessuna versione disponibile soddisfa {compatibilityLabel}. Puoi selezionare una versione da installare comunque. Le dipendenze non verranno scaricate automaticamente."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
@@ -215,9 +224,6 @@
|
||||
"app.instance.mods.project-was-added": {
|
||||
"message": "\"{name}\" è stato aggiunto"
|
||||
},
|
||||
"app.instance.mods.projects-were-added": {
|
||||
"message": "{count} progetti aggiunti"
|
||||
},
|
||||
"app.instance.mods.share-text": {
|
||||
"message": "Guarda le mod che uso nel mio pacchetto!"
|
||||
},
|
||||
@@ -272,6 +278,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Installa"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count} mod"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Pacchetto di mod richiesto"
|
||||
},
|
||||
@@ -333,7 +342,7 @@
|
||||
"message": "Nuova skin"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "Trascina e rilascia"
|
||||
"message": "Trascina qui"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Applica"
|
||||
@@ -348,7 +357,7 @@
|
||||
"message": "Vuoi davvero eliminare questa skin?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Non è stato possibile leggere il file rilasciato."
|
||||
"message": "Non è stato possibile leggere il file trascinato."
|
||||
},
|
||||
"app.skins.dropped-file-error.title": {
|
||||
"message": "Impossibile processare il file"
|
||||
@@ -357,13 +366,13 @@
|
||||
"message": "Modifica"
|
||||
},
|
||||
"app.skins.modal.add-skin-button": {
|
||||
"message": "Salva"
|
||||
"message": "Aggiungi"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "Aggiungi una nuova skin"
|
||||
"message": "Aggiungi una skin"
|
||||
},
|
||||
"app.skins.modal.arm-style-section": {
|
||||
"message": "Modello del giocatore"
|
||||
"message": "Modello"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "Snello"
|
||||
@@ -381,7 +390,7 @@
|
||||
"message": "Modifica la skin"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "Modifica la skin prima!"
|
||||
"message": "Modifica prima la skin!"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "Nessun mantello"
|
||||
@@ -390,19 +399,19 @@
|
||||
"message": "Nessuno"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "Sfoglia"
|
||||
"message": "Cambia"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "Salva"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "Salvataggio..."
|
||||
"message": "Salvando..."
|
||||
},
|
||||
"app.skins.modal.texture-section": {
|
||||
"message": "File della skin"
|
||||
"message": "Aspetto"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "Carica una skin prima!"
|
||||
"message": "Carica prima una skin!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "Modifica"
|
||||
@@ -411,11 +420,17 @@
|
||||
"message": "Anteprima"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "Stai modificando la skin troppo spesso. I server di Mojang hanno limitato le nostre richieste. Aspetta un momento prima di riprovare."
|
||||
"message": "Stai cambiando skin troppo spesso. I server di Mojang hanno limitato le nostre richieste. Aspetta un momento prima di riprovare."
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Rallenta!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "L'ordine delle skin non è stato salvato."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Errore nel riordine delle skin"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -441,7 +456,7 @@
|
||||
"message": "Mounts of Mayhem"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "Raccolta"
|
||||
"message": "Skin salvate"
|
||||
},
|
||||
"app.skins.section.striding-hero": {
|
||||
"message": "Striding Hero"
|
||||
@@ -459,13 +474,13 @@
|
||||
"message": "Accedi"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "Accedi col tuo account Minecraft per usare la gestione delle skin."
|
||||
"message": "Accedi col tuo account Minecraft per poter gestire le skin qui."
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "Modrinth Bot emozionato"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "Accedi"
|
||||
"message": "Effettua l'accesso"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Seleziona una skin"
|
||||
@@ -522,7 +537,7 @@
|
||||
"message": "Stringi un'amicizia"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, one {richiesta} other {richieste}} d''amicizia"
|
||||
"message": "{count, plural, one {# richiesta} other {# richieste}} d''amicizia"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Invia richiesta d'amicizia"
|
||||
@@ -689,6 +704,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nome"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Canale di aggiornamento"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Le versioni stabili, beta e alpha saranno mostrate come aggiornamenti disponibili."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Le versioni stabili e beta saranno mostrate come aggiornamenti disponibili."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Stabile"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Solo le versioni stabili saranno mostrate come aggiornamenti disponibili."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Seleziona il canale di aggiornamento"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Hook di avvio"
|
||||
},
|
||||
@@ -738,16 +777,16 @@
|
||||
"message": "Java e memoria"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-environment-variables": {
|
||||
"message": "Variabili d'ambiente"
|
||||
"message": "Personalizzate"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-arguments": {
|
||||
"message": "Argomenti JVM"
|
||||
"message": "Personalizzati"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-installation": {
|
||||
"message": "Eseguibile di Java"
|
||||
"message": "Personalizzata"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-memory-allocation": {
|
||||
"message": "Memoria allocata"
|
||||
"message": "Personalizzata"
|
||||
},
|
||||
"instance.settings.tabs.java.enter-environment-variables": {
|
||||
"message": "Inserisci le variabili d'ambiente..."
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} をダウンロード中"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "アップデートをダウンロード中"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "ダウンロード"
|
||||
},
|
||||
@@ -42,7 +45,7 @@
|
||||
"message": "高度なレンダリング"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Modrinth Appでのお好みのテーマを選択してください"
|
||||
"message": "Modrinth Appでのお好みのテーマを選択してください。"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "テーマ"
|
||||
@@ -60,25 +63,25 @@
|
||||
"message": "デフォルトの起動ページ"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "スキンページで、プレイヤー名の上にある名前タグを無効にします。"
|
||||
"message": "スキンページでプレイヤー上部のネームタグを非表示にします。"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "名札を非表示にする"
|
||||
"message": "ネームタグを非表示にする"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "ホームページの「ジャンプバック」セクションには、最近追加されたワールドが含まれています。"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "再び世界へ飛び込もう"
|
||||
"message": "最近のワールドを表示"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minecraftが起動した時、ランチャーを最小化します"
|
||||
"message": "Minecraftが起動した時、ランチャーを最小化します。"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "ランチャーを最小化"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "OSデフォルトのウィンドウUIを使用します(アプリの再起動が必要です)"
|
||||
"message": "OSデフォルトのウィンドウUIを使用します。(アプリの再起動が必要です)"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "OSのウィンドウUIを使用"
|
||||
@@ -86,17 +89,23 @@
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "オプションを選択してください"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "インスタンスのプレイ時間を表示します。"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "プレイ時間を表示"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "サイドバーの表示/非表示を切り替える機能を有効にします。"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "サイドバーの表示/非表示を切り替える"
|
||||
"message": "サイドバーの表示切替を有効化"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Modrinth上にホストされていないModrinth Packファイル(.mrpack)をインストールしようとした場合、インストール前にそのリスクを理解していただけるよう確認します。"
|
||||
"message": "Modrinthで配布されていないModrinth Pack(.mrpack)をインストールする場合、事前にリスクについて警告します。"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "未知のMODパックをインストールする前に警告してください"
|
||||
"message": "不明なModパックのインストール前に警告する"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecraftの認証サーバーは現在停止している可能性があります。インターネット接続を確認し、しばらくしてからもう一度お試しください。"
|
||||
@@ -254,6 +263,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "インストール"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {#個のMod}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "必須のModパック"
|
||||
},
|
||||
@@ -302,6 +314,21 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "リソース管理"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "スリム"
|
||||
},
|
||||
"app.skins.modal.arm-style-wide": {
|
||||
"message": "ワイド"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "マント"
|
||||
},
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "マント"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "マントなし"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "置きかえる"
|
||||
},
|
||||
@@ -311,6 +338,42 @@
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "編集"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "ビルダーズ&バイオーム"
|
||||
},
|
||||
"app.skins.section.chase-the-skies": {
|
||||
"message": "チェイス・ザ・スカイ"
|
||||
},
|
||||
"app.skins.section.minecon-earth-2017": {
|
||||
"message": "MINECON Earth 2017"
|
||||
},
|
||||
"app.skins.section.modrinth": {
|
||||
"message": "Modrinth"
|
||||
},
|
||||
"app.skins.section.modrinth-pride": {
|
||||
"message": "Modrinth プライド"
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "マウント・オブ・メイヘム"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "保存済みのスキン"
|
||||
},
|
||||
"app.skins.section.striding-hero": {
|
||||
"message": "Striding Hero"
|
||||
},
|
||||
"app.skins.section.the-copper-age": {
|
||||
"message": "銅の時代"
|
||||
},
|
||||
"app.skins.section.the-garden-awakens": {
|
||||
"message": "ガーデン・アウェイケンズ"
|
||||
},
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "タイニー・テイクオーバー"
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "ワクワクしている Modrinth ボット"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App v{version} のダウンロードが完了しました。今すぐ更新するには再読み込みするか、Modrinth Appを閉じる際に自動更新されます。"
|
||||
},
|
||||
@@ -329,9 +392,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "ダウンロード完了"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "再読み込み"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "アップデートが利用可能です"
|
||||
},
|
||||
@@ -362,6 +422,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "フレンドを追加"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count}件の友達リクエスト"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "フレンド申請を送信"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} 다운로드 중"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "업데이트 다운로드중"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "다운로드"
|
||||
},
|
||||
@@ -500,9 +503,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "다운로드 완료"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "새로고침"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "업데이트 가능"
|
||||
},
|
||||
@@ -533,6 +533,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "친구 추가"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count}건의 친구 요청"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "친구 요청 보내기"
|
||||
},
|
||||
|
||||
@@ -275,6 +275,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Pasang"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# mod}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Pek mod yang diperlukan"
|
||||
},
|
||||
@@ -455,9 +458,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "Muat turun selesai"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Muat semula"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Kemas kini tersedia"
|
||||
},
|
||||
@@ -488,6 +488,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Tambah rakan"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, other {permintaan}} rakan"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Hantar permintaan rakan"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} aan het downloaden"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Update aan het downloaden"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primaire instantie"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Herlaadt om te updaten"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Toon meer actieve instanties"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Stop instantie"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Updaten"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Bekijk actieve downloads"
|
||||
},
|
||||
@@ -86,6 +95,12 @@
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Selecteer een optie"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Toont hoeveel tijd je aan een instantie hebt besteed."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Speeltijd laten zien"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Schakelt de mogelijkheid om de zijbalk in- of uit te schakelen in."
|
||||
},
|
||||
@@ -143,6 +158,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installeren"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Er zijn geen beschikbare versies die overeenkomen met {compatibilityLabel}. Selecteer toch een versie om te installeren. Afhankelijkheden worden niet automatisch geïnstalleerd."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -254,6 +272,9 @@
|
||||
"app.instance.worlds.remove-server-title": {
|
||||
"message": "Ben je zeker dat je {name} wil verwijderen?"
|
||||
},
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "Zoek werelden"
|
||||
},
|
||||
"app.instance.worlds.this-server": {
|
||||
"message": "deze server"
|
||||
},
|
||||
@@ -296,6 +317,9 @@
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Dit project is al geïnstalleerd"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Terug naar Ontdekken"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installeer content naar instantie"
|
||||
},
|
||||
@@ -323,6 +347,153 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "Bronnenbeheer"
|
||||
},
|
||||
"app.skins.add-button": {
|
||||
"message": "Skin toevoegen"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "Slepen en neerzetten"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Toepassen"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "Skin verwijderen"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"message": "De geselecteerde skin wordt permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt."
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "Weet u zeker dat u deze skin wilt verwijderen?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Kan het neergezette bestand niet lezen."
|
||||
},
|
||||
"app.skins.dropped-file-error.title": {
|
||||
"message": "Fout bij het verwerken van het bestand"
|
||||
},
|
||||
"app.skins.edit-button": {
|
||||
"message": "Skin aanpassen"
|
||||
},
|
||||
"app.skins.modal.add-skin-button": {
|
||||
"message": "Skin toevoegen"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "Voeg een skin toe"
|
||||
},
|
||||
"app.skins.modal.arm-style-section": {
|
||||
"message": "Armstijl"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "Slank"
|
||||
},
|
||||
"app.skins.modal.arm-style-wide": {
|
||||
"message": "Breed"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "Cape"
|
||||
},
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "Cape"
|
||||
},
|
||||
"app.skins.modal.edit-title": {
|
||||
"message": "Skin aan het aanpassen"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "Verander eerst iets aan de skin!"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "Geen cape"
|
||||
},
|
||||
"app.skins.modal.none-cape-option": {
|
||||
"message": "Geen"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "Texture vervangen"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "Skin opslaan"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "Opslaan..."
|
||||
},
|
||||
"app.skins.modal.texture-section": {
|
||||
"message": "Textuur"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "Upload eerst een skin!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "Skin aanpassen"
|
||||
},
|
||||
"app.skins.previewing-badge": {
|
||||
"message": "Voorvertonen"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "U verandert te snel van skin. Mojang's servers hebben nieuwe aanvragen tijdelijk geblokkeerd. Wacht een moment voordat u het opnieuw probeert."
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Rustig aan!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Je skinvolgorde kon niet worden opgeslagen."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Het herschikken van skins is mislukt."
|
||||
},
|
||||
"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": "Standaardskins"
|
||||
},
|
||||
"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": "U heeft deze skins ontvangen omdat u tijdens Pride Month heeft gedoneerd aan een Modrinth Pride-inzamelingsactie."
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "Mounts of Mayhem"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "Opgeslagen skins"
|
||||
},
|
||||
"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": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Inloggen"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "Log in op je Minecraft-account om de skinbeheerfuncties van de Modrinth-app te gebruiken."
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "Excited Modrinth Bot"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "Log in"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Skinkiezer"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App v{version} is klaar met downloaden. Herlaad om nu te updaten, of automatisch wanneer je de Modrinth App afsluit."
|
||||
},
|
||||
@@ -342,7 +513,7 @@
|
||||
"message": "Downloaden voltooid"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Herlaad"
|
||||
"message": "Herladen om te updaten"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Update beschikbaar"
|
||||
@@ -542,6 +713,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Naam"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Updatekanaal"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Release-, beta- en alfaversies worden als beschikbare updates weergegeven."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Release- en beta-versies worden als beschikbare updates weergegeven.\n"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Release"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Alleen releaseversies worden als beschikbare updates weergegeven."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Selecteer updatekanaal"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Opstart haakjes"
|
||||
},
|
||||
@@ -590,6 +785,24 @@
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java en geheugen"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-environment-variables": {
|
||||
"message": "Aangepaste omgevingsvariabelen"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-arguments": {
|
||||
"message": "Aangepaste Java-argumenten"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-installation": {
|
||||
"message": "Aangepaste Java-installatie"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-memory-allocation": {
|
||||
"message": "Aangepaste geheugentoewijzing"
|
||||
},
|
||||
"instance.settings.tabs.java.enter-environment-variables": {
|
||||
"message": "Voer omgevingsvariabelen in…"
|
||||
},
|
||||
"instance.settings.tabs.java.enter-java-arguments": {
|
||||
"message": "Voer Java-argumenten in…"
|
||||
},
|
||||
"instance.settings.tabs.java.environment-variables": {
|
||||
"message": "Environment variabelen"
|
||||
},
|
||||
@@ -605,6 +818,9 @@
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "Voorgeschreven geheugen"
|
||||
},
|
||||
"instance.settings.tabs.java.java-path-placeholder": {
|
||||
"message": "//pad/naar/java"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "Venster"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Pobieranie Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Pobieranie aktualizacji"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Pobrania"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Główna instancja"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Załaduj ponownie, by zaktualizować"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Pokaż więcej włączonych instancji"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Zatrzymaj instancję"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Zaktualizuj"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Pokaż aktualnie pobierane"
|
||||
},
|
||||
@@ -270,7 +279,7 @@
|
||||
"message": "ten serwer"
|
||||
},
|
||||
"app.modal.install-to-play.content-required": {
|
||||
"message": "Wymagane treści"
|
||||
"message": "Wymagana zawartość"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "Zainstaluj, aby grać"
|
||||
@@ -278,6 +287,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Zainstaluj"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# mod} few {# mody} other {# modów}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Wymagana paczka modów"
|
||||
},
|
||||
@@ -422,6 +434,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Zwolnij!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Nie udało się zapisać kolejności skórek."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Nie udało się zmienić kolejności skórek"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -495,7 +513,7 @@
|
||||
"message": "Pobieranie ukończone"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Załaduj ponownie"
|
||||
"message": "Załaduj ponownie, by zaktualizować"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Dostępna aktualizacja"
|
||||
@@ -527,6 +545,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Dodaj znajomego"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, one {zaproszenie} few {zaproszenia} other {zaproszeń}} do znajomych"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Wyślij zaproszenie"
|
||||
},
|
||||
@@ -692,6 +713,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nazwa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Kanał aktualizacji"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Wersje stabilne, beta i alfa będą pokazane jako dostępne aktualizacje."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Tylko wersje stabilne i wersje beta będą pokazane jako dostępne aktualizacje."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Stabilny"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Tylko wersje stabilne będą pokazane jako dostępne aktualizacje."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Wybierz kanał aktualizacji"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Haczyki startowe"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Baixando Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Baixando atualização"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instância principal"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Recarregar para atualizar"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Exibir mais instâncias em execução"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Parar instância"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Atualizar"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Ver downloads ativos"
|
||||
},
|
||||
@@ -42,7 +51,7 @@
|
||||
"message": "Renderização avançada"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Selecione seu tema de cores preferido para o Modrinth App."
|
||||
"message": "Escolha seu tema de cores preferido para o Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema de cores"
|
||||
@@ -99,7 +108,7 @@
|
||||
"message": "Alternar barra lateral"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Se você tentar instalar um arquivo Modrinth Pack (.mrpack) não hospedado no Modrinth, garantiremos que você entenda os riscos antes de instalá-lo."
|
||||
"message": "Se você tentar instalar um arquivo Modrinth Pack (.mrpack) não hospedado no Modrinth, garantiremos que você entenda os riscos antes de instalar."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Avise-me antes de instalar pacotes de mods desconhecidos"
|
||||
@@ -281,6 +290,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Instalar"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, =0 {Nenhum mod} one {# mod} other {# mods}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Pacote de mods necessário"
|
||||
},
|
||||
@@ -425,6 +437,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Mais lento!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Não foi possível salvar a ordem de skins."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Falhou ao reordenar skins"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Construtores & Biomas"
|
||||
},
|
||||
@@ -456,7 +474,7 @@
|
||||
"message": "Striding Hero"
|
||||
},
|
||||
"app.skins.section.the-copper-age": {
|
||||
"message": "A Era do Cobre"
|
||||
"message": "The Copper Age"
|
||||
},
|
||||
"app.skins.section.the-garden-awakens": {
|
||||
"message": "The Garden Awakens"
|
||||
@@ -468,7 +486,7 @@
|
||||
"message": "Iniciar sessão"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "Faça login na sua conta do Minecraft para usar os recursos de gerenciamento de skins do Modrinth app."
|
||||
"message": "Inicie sessão na sua conta do Minecraft para usar os recursos de gerenciamento de skins no Modrinth App."
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "Modrinth Bot animado"
|
||||
@@ -480,10 +498,10 @@
|
||||
"message": "Seletor de skins"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "O Modrinth App v{version} foi baixado. Recarregue para atualizar agora ou a atualização será aplicada automaticamente ao fechar o Modrinth App."
|
||||
"message": "O Modrinth App v{version} já foi instalado. Recarregue para atualizar agora ou ela será feita automaticamente ao fechar o Modrinth App."
|
||||
},
|
||||
"app.update-popup.body.linux": {
|
||||
"message": "O Modrinth App v{version} está disponível. Use seu gerenciador de pacotes para atualizar e obter os recursos e correções mais recentes!"
|
||||
"message": "O Modrinth App v{version} está disponível. Use o gerenciador de pacotes para atualizar para obter novos recursos e correções!"
|
||||
},
|
||||
"app.update-popup.body.metered": {
|
||||
"message": "O Modrinth App v{version} já está disponível! Já que sua rede é limitada, não a instalamos automaticamente."
|
||||
@@ -498,7 +516,7 @@
|
||||
"message": "Instalação concluída"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Recarregar"
|
||||
"message": "Recarregar para atualizar"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Atualização disponível"
|
||||
@@ -552,13 +570,13 @@
|
||||
"message": "<link>Adicione amigos</link> para ver o que eles estão jogando!"
|
||||
},
|
||||
"friends.friend.cancel-request": {
|
||||
"message": "Cancelar solicitação"
|
||||
"message": "Cancelar pedido"
|
||||
},
|
||||
"friends.friend.remove-friend": {
|
||||
"message": "Remover amigo"
|
||||
},
|
||||
"friends.friend.request-sent": {
|
||||
"message": "Solicitação de amizade enviada"
|
||||
"message": "Pedido de amizade enviado"
|
||||
},
|
||||
"friends.friend.view-profile": {
|
||||
"message": "Ver perfil"
|
||||
@@ -612,7 +630,7 @@
|
||||
"message": "Editar servidor"
|
||||
},
|
||||
"instance.edit-world.hide-from-home": {
|
||||
"message": "Esconder da página inicial"
|
||||
"message": "Ocultar da página inicial"
|
||||
},
|
||||
"instance.edit-world.name": {
|
||||
"message": "Nome"
|
||||
@@ -654,7 +672,7 @@
|
||||
"message": "Excluir instância"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.description": {
|
||||
"message": "Exclui permanentemente uma instância do seu dispositivo, incluindo seus mundos, configurações e todo o conteúdo instalado. Tome cuidado, pois, uma vez excluída, a instância não poderá ser recuperada."
|
||||
"message": "Exclui permanentemente uma instância do seu dispositivo, incluindo seus mundos, configs e todo o conteúdo instalado. Tome cuidado, assim que excluído, a instância não pode ser recuperada."
|
||||
},
|
||||
"instance.settings.tabs.general.deleting.button": {
|
||||
"message": "Excluindo..."
|
||||
@@ -669,7 +687,7 @@
|
||||
"message": "Duplicar instância"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance.description": {
|
||||
"message": "Cria uma cópia desta instância, incluindo mundos, configurações, mods, etc."
|
||||
"message": "Cria uma cópia desta instância, incluindo mundos, configs, mods, etc."
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "Editar ícone"
|
||||
@@ -687,7 +705,7 @@
|
||||
"message": "Grupos"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.create": {
|
||||
"message": "Criar novo grupo"
|
||||
"message": "Criar grupo novo"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.description": {
|
||||
"message": "Os grupos da biblioteca permitem que você organize suas instâncias em diferentes seções."
|
||||
@@ -698,6 +716,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nome"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Canal de atualização"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "As versões release, beta e alpha aparecerão como atualizações disponíveis."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "As versões release e beta aparecerão como atualizações disponíveis."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Release"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Apenas versões release aparecerão como atualizações disponíveis."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Selecionar canal de atualização"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Ações de inicialização"
|
||||
},
|
||||
@@ -705,7 +747,7 @@
|
||||
"message": "Ações de inicialização personalizadas"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "Essas ações permitem que usuários mais experientes executem comandos do sistema antes e depois de inicializar o jogo."
|
||||
"message": "As ações permitem usuários experientes que executem certos comandos do sistema antes ou após iniciar o jogo."
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit": {
|
||||
"message": "Ao sair"
|
||||
@@ -714,16 +756,16 @@
|
||||
"message": "Executado após o jogo fechar."
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.enter": {
|
||||
"message": "Insira o comando a ser executado após o jogo fechar..."
|
||||
"message": "Insira o comando ao sair..."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch": {
|
||||
"message": "Pré-inicialização"
|
||||
"message": "Ao iniciar"
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.description": {
|
||||
"message": "Executado antes que a instância seja inicializada."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.enter": {
|
||||
"message": "Insira o comando a ser executado antes da inicialização..."
|
||||
"message": "Insira o comando ao iniciar..."
|
||||
},
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Ações de inicialização do jogo"
|
||||
@@ -735,13 +777,13 @@
|
||||
"message": "Comando auxiliar para iniciar o Minecraft."
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.enter": {
|
||||
"message": "Insira um comando..."
|
||||
"message": "Insira um comando auxiliar..."
|
||||
},
|
||||
"instance.settings.tabs.installation": {
|
||||
"message": "Instalação"
|
||||
},
|
||||
"instance.settings.tabs.installation.loader-version": {
|
||||
"message": "Versão do {loader}"
|
||||
"message": "Versão {loader}"
|
||||
},
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java e memória"
|
||||
@@ -786,13 +828,13 @@
|
||||
"message": "Janela"
|
||||
},
|
||||
"instance.settings.tabs.window.custom-window-settings": {
|
||||
"message": "Configurações de janela personalizada"
|
||||
"message": "Opções de janela personalizada"
|
||||
},
|
||||
"instance.settings.tabs.window.fullscreen": {
|
||||
"message": "Tela cheia"
|
||||
},
|
||||
"instance.settings.tabs.window.fullscreen.description": {
|
||||
"message": "Faz com que o jogo inicie em tela cheia (usando options.txt)."
|
||||
"message": "Faz o jogo iniciar em tela cheia (usando options.txt)."
|
||||
},
|
||||
"instance.settings.tabs.window.height": {
|
||||
"message": "Altura"
|
||||
@@ -822,7 +864,7 @@
|
||||
"message": "Copiar endereço"
|
||||
},
|
||||
"instance.worlds.dont_show_on_home": {
|
||||
"message": "Não mostrar no início"
|
||||
"message": "Não exibir na página inicial"
|
||||
},
|
||||
"instance.worlds.game_already_open": {
|
||||
"message": "A instância já está aberta"
|
||||
@@ -834,19 +876,19 @@
|
||||
"message": "Servidor incompatível"
|
||||
},
|
||||
"instance.worlds.linked_server": {
|
||||
"message": "Gerenciado pelo projeto de servidor"
|
||||
"message": "Gerenciado pelo projeto do servidor"
|
||||
},
|
||||
"instance.worlds.no_contact": {
|
||||
"message": "Não foi possível conectar-se ao servidor"
|
||||
},
|
||||
"instance.worlds.no_server_quick_play": {
|
||||
"message": "Você só pode entrar diretamente em servidores a partir do Minecraft 1.0.5"
|
||||
"message": "Só é possível entrar diretamente em servidores a partir do Minecraft Alpha 1.0.5"
|
||||
},
|
||||
"instance.worlds.no_singleplayer_quick_play": {
|
||||
"message": "Você só pode entrar diretamente em mundos de um jogador a partir do Minecraft 1.20+"
|
||||
"message": "Só é possível entrar em mundos um jogador diretamente a partir do Minecraft 1.20"
|
||||
},
|
||||
"instance.worlds.play_instance": {
|
||||
"message": "Jogar na instância"
|
||||
"message": "Iniciar instância"
|
||||
},
|
||||
"instance.worlds.view_instance": {
|
||||
"message": "Ver instância"
|
||||
@@ -858,7 +900,7 @@
|
||||
"message": "Adicionar conta"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Conta do minecraft"
|
||||
"message": "Conta de Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Não conectado"
|
||||
@@ -870,7 +912,7 @@
|
||||
"message": "Selecionar conta"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Faça login no Minecraft"
|
||||
"message": "Inicie sessão no Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Fornecido pela instância"
|
||||
@@ -879,7 +921,7 @@
|
||||
"message": "A versão do jogo é fornecida pela instância"
|
||||
},
|
||||
"search.filter.locked.instance-loader.title": {
|
||||
"message": "O carregador é fornecido pela instância"
|
||||
"message": "O loader é fornecido pela instância"
|
||||
},
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar com a instância"
|
||||
@@ -888,19 +930,19 @@
|
||||
"message": "Fornecido pelo servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Apenas mods do lado do cliente podem ser adicionados à instância do servidor"
|
||||
"message": "Somente mods do cliente podem ser adicionados à instância do servidor"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "A versão do jogo é fornecida pelo servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "O carregador é fornecido pelo servidor"
|
||||
"message": "O loader é fornecido pelo servidor"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Um arquivo só é revisado se for enviado para o Modrinth, não importa o seu formato de arquivo (incluindo .mrpack)."
|
||||
"message": "Um arquivo só é revisado se for enviado no Modrinth, independente do formato do arquivo (incluindo .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Não mostrar este aviso novamente"
|
||||
"message": "Não exibir aviso novamente"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Confirmar instalação"
|
||||
@@ -909,10 +951,10 @@
|
||||
"message": "Instalar mesmo assim"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Malware é frequentemente distribuído através de arquivos de pacotes de mods compartilhados em plataformas como o Discord."
|
||||
"message": "O malware é distribuído frequentemente através de arquivos de pacote de mods compartilhados em plataformas como Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Não foi possível encontrar este arquivo no Modrinth. Recomendamos fortemente que você instale apenas arquivos de fontes confiáveis."
|
||||
"message": "Não encontramos este arquivo no Modrinth. Nós recomendamos fortemente instalar arquivos apenas de fontes confiáveis."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Aviso de arquivo desconhecido"
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "A descarregar a versão do java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "A transferir atualização"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instância principal"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Recarregar para atualizar"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostrar mais instâncias a correr"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Para instância"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Atualizar"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Ver instâncias ativas"
|
||||
},
|
||||
@@ -35,6 +44,9 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Ver logs"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Ativa a renderização avançada, como efeitos de desfoque, que podem causar problemas de desempenho sem o uso de renderização acelerada por hardware."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderização avançada"
|
||||
},
|
||||
@@ -56,6 +68,12 @@
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Página de carregamento principal"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimizar launcher"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Seleciona uma opção"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Os servidores de autenticação do Minecraft poderão estar em baixo de momento. Verifica a tua ligação à internet e tenta novamente mais tarde."
|
||||
},
|
||||
@@ -80,6 +98,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Descobrir servidores"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Insere a descrição do modpack..."
|
||||
},
|
||||
@@ -239,6 +260,15 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "Gestão de recursos"
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "Tens a certeza que queres apagar esta skin?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Não foi possível ler o ficheiro largado."
|
||||
},
|
||||
"app.skins.section.modrinth": {
|
||||
"message": "Modrinth"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App v{version} acabou de ser transferida. Recarrega para atualizar agora, ou automaticamente quando fechares a Modrinth App."
|
||||
},
|
||||
@@ -257,9 +287,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "Transferência concluída"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Recarregar"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Atualização disponível"
|
||||
},
|
||||
|
||||
@@ -83,9 +83,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "Descărcare finalizată"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Reîncărcați"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Actualizare disponibilă"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Скачивание Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Скачивание"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Загрузки"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Основная сборка"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Перезапустить"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Показать другие активные сборки"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Остановить сборку"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Обновить"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Посмотреть текущие загрузки"
|
||||
},
|
||||
@@ -266,6 +275,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Установить"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# мод} few {# мода} other {# модов}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Необходимая сборка"
|
||||
},
|
||||
@@ -410,6 +422,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Не так быстро!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Ваш порядок скинов не удалось сохранить."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Не удалось изменить порядок скинов"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -515,6 +533,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Добавить в друзья"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, one {запрос} few {запроса} other {запросов}} дружбы"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Отправить запрос дружбы"
|
||||
},
|
||||
@@ -677,6 +698,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Название"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Канал обновления"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Альфа"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Релизные, бета и альфа версии будут отображаться как доступные обновления."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Бета"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Релизные и бета версии будут отображаться как доступные обновления."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Релиз"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Только релизные версии будут отображаться как доступные обновления."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Выберите канал обновления"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Команды запуска"
|
||||
},
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Laddar ner Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Laddar ner uppdatering"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Nedladdningar"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Göm fler pågående instanser"
|
||||
"message": "Göm fler aktiva instanser"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Gör till primärinstans"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Ingen pågående instans"
|
||||
"message": "Ingen aktiva instans"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primärinstans"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Ladda om för att uppdatera"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Visa fler pågående instanser"
|
||||
"message": "Visa fler aktiva instanser"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Avsluta instans"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Uppdatera"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Visa aktiva nedladdningar"
|
||||
},
|
||||
@@ -65,6 +74,9 @@
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Dölj namnskylt"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Hoppa tillbaka i världar"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimera programstartare"
|
||||
},
|
||||
@@ -77,18 +89,33 @@
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Välj ett alternativ"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Visar hur mycket tid du spelat på en instans."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Visa speltid"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Låter dig växla sidofältet."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Växla sidofält"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Om du försöker installera en Modrinth Pack fil (.mrpack) som inte kommer från Modrinth ser vi till att du känner till riskerna innan du installerar den."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Varna mig innan jag installerar okända modpaket"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecrafts autentiseringsservrar kan vara nere just nu. Kontrollera din internetanslutning och försök igen senare."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Kan ej nå autentiseringsservrarna"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Lägger till server till instans"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Lägg till i en instans"
|
||||
},
|
||||
@@ -122,6 +149,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installerar"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Ingen tillgänglig version matchar {compatibilityLabel}. Välj en version att installera ändå. Beroenden kommer inte installeras automatiskt."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -146,6 +176,9 @@
|
||||
"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"
|
||||
},
|
||||
@@ -153,7 +186,7 @@
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "All data från din instans kommer att raderas permanent, inklusive dina världar, konfigurationer och allt installerat innehåll."
|
||||
"message": "All data från din instans kommer permanent raderas, däribland dina världar, konfigurationer samt allt installerat innehåll."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "Detta kan inte ogöras"
|
||||
@@ -275,6 +308,12 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "En uppdatering krävs för att spela {name}. Vänligen uppdatera till senaste version för att starta spelet."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Detta projekt är redan installerat"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installera innehåll till instans"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Utvecklarläge aktiverat."
|
||||
},
|
||||
@@ -302,9 +341,27 @@
|
||||
"app.skins.add-button": {
|
||||
"message": "Lägg till utseende"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "Dra och släpp"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Tillämpa"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "Ta bort utseende"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"message": "Du kommer permanent radera det valda utseendet. Detta kan inte ångras."
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "Är du säker på att du vill radera utseendet?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Misslyckades att läsa den släppta filen."
|
||||
},
|
||||
"app.skins.dropped-file-error.title": {
|
||||
"message": "Fel med behandlingen av filen"
|
||||
},
|
||||
"app.skins.edit-button": {
|
||||
"message": "Redigera utseende"
|
||||
},
|
||||
@@ -353,6 +410,69 @@
|
||||
"app.skins.modal.texture-section": {
|
||||
"message": "Textur"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "Ladda upp utseende först!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "Redigera utseende"
|
||||
},
|
||||
"app.skins.previewing-badge": {
|
||||
"message": "Förhandsvisa"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "Du ändrar ditt utseende för ofta. Mojangs servrar har temporärt blockerat ytterligare förfrågningar. Vänligen vänta en stund innan du försöker igen."
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Sakta ner!"
|
||||
},
|
||||
"app.skins.section.chase-the-skies": {
|
||||
"message": "Chase the Skies"
|
||||
},
|
||||
"app.skins.section.default-skins": {
|
||||
"message": "Standard utseenden"
|
||||
},
|
||||
"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": "Du har fått dessa utseende eftersom du donerat till en Modrinth Pride insamling under pride månaden."
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "Mounts of Mayhem"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "Sparade utseenden"
|
||||
},
|
||||
"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": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Logga in"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "Snälla logga in din Minecraft konto att använda hud hantering förmågor i Modrinth appen."
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "Exalterad Modrinth bot"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "Vänligen logga in"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Utseende väljare"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App v{version} har laddats ner. Ladda om för att uppdatera nu, eller automatiskt när du stänger Modrinth App."
|
||||
},
|
||||
@@ -372,7 +492,7 @@
|
||||
"message": "Nedladdning slutförd"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Ladda om"
|
||||
"message": "Ladda om för att uppdatera"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Uppdatering tillgänglig"
|
||||
@@ -572,6 +692,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Namn"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Uppdateringskanal"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Utgåvor, beta och alfa versioner visas som tillgängliga uppdateringar."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Utgåvor och beta versioner visas som tillgängliga uppdateringar."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Utgåva"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Endast utgivna versioner visas som tillgängliga uppdateringar."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Välj uppdateringskanal"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Startkrokar"
|
||||
},
|
||||
@@ -620,6 +764,24 @@
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java och minne"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-environment-variables": {
|
||||
"message": "Anpassade miljövariabler"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-arguments": {
|
||||
"message": "Anpassade Java-argument"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-java-installation": {
|
||||
"message": "Anpassad Java-installation"
|
||||
},
|
||||
"instance.settings.tabs.java.custom-memory-allocation": {
|
||||
"message": "Anpassad minnestilldelning"
|
||||
},
|
||||
"instance.settings.tabs.java.enter-environment-variables": {
|
||||
"message": "Skriv in miljövariabler..."
|
||||
},
|
||||
"instance.settings.tabs.java.enter-java-arguments": {
|
||||
"message": "Skriv in Java-argument..."
|
||||
},
|
||||
"instance.settings.tabs.java.environment-variables": {
|
||||
"message": "Miljövariabler"
|
||||
},
|
||||
@@ -635,6 +797,9 @@
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "Tilldelat minne"
|
||||
},
|
||||
"instance.settings.tabs.java.java-path-placeholder": {
|
||||
"message": "/sökväg/till/java"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "Fönster"
|
||||
},
|
||||
@@ -651,7 +816,7 @@
|
||||
"message": "Höjd"
|
||||
},
|
||||
"instance.settings.tabs.window.height.description": {
|
||||
"message": "Höjden på spel-fönstret vid uppstart."
|
||||
"message": "Höjden på spelfönstret vid uppstart."
|
||||
},
|
||||
"instance.settings.tabs.window.height.enter": {
|
||||
"message": "Ange höjd..."
|
||||
@@ -660,7 +825,7 @@
|
||||
"message": "Bredd"
|
||||
},
|
||||
"instance.settings.tabs.window.width.description": {
|
||||
"message": "Bredden på spel-fönstret vid uppstart."
|
||||
"message": "Bredden på spelfönstret vid uppstart."
|
||||
},
|
||||
"instance.settings.tabs.window.width.enter": {
|
||||
"message": "Ange bredd..."
|
||||
@@ -749,7 +914,25 @@
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader tillhandahålls av servern"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "En fil granskas bara om den laddas upp till Modrinth, oavsett dess filformat (däribland .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Visa inte denna varning igen"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Bekräfta installation"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "Installera ändå"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Skadeprogram distribueras ofta via modpaketfiler genom att dela dem på plattformar som Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Vi kunde inte hitta filen på Modrinth. Vi rekommenderar starkt att endast installera filer från källor du litar på."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Okänd filvarning"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "ดาวน์โหลดเวอร์ชัน Java {version}"
|
||||
"message": "กำลังดาวน์โหลด Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "กำลังดาวน์โหลดอัปเดต"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "ดาวน์โหลด"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "ซ่อนการดำเนินการโปรแกรมเบื้องหลัง"
|
||||
"message": "ซ่อนอินสแตนซ์ที่ทำงานอยู่เพิ่มเติม"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "กำหนดโปรแกรมหลัก"
|
||||
"message": "กำหนดอินสแตนซ์หลัก"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "ไม่มีโปรแกรมใดดำเนินการ"
|
||||
"message": "ไม่มีอินสแตนซ์ที่กำลังทำงาน"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "ออฟไลน์"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "โปรแกรมหลัก"
|
||||
"message": "อินสแตนซ์หลัก"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "รีโหลดเพื่ออัปเดต"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "แสดงการดำเนินการโปรแกรมเบื้องหลังเพิ่มเติม"
|
||||
"message": "แสดงอินสแตนซ์ที่ทำงานอยู่เพิ่มเติม"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "หยุดการทำงานโปรแกรม"
|
||||
"message": "หยุดอินสแตนซ์"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "อัปเดต"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "ดูยอดการดาวน์โหลดที่ยังใช้งาน"
|
||||
"message": "ดูการดาวน์โหลดที่กำลังดำเนินการอยู่"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "ดูโปรแกรม"
|
||||
"message": "ดูอินสแตนซ์"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "ดู Log"
|
||||
@@ -42,7 +51,7 @@
|
||||
"message": "การเรนเดอร์ขั้นสูง"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "เลือกธีมสีที่คุณชื่นชอบสำหรับ Modrinth บนอุปกรณ์นี้"
|
||||
"message": "เลือกธีมสีที่คุณต้องการสำหรับ Modrinth App"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "ธีมสี"
|
||||
@@ -54,22 +63,22 @@
|
||||
"message": "หน้าหลัก"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "รายการ"
|
||||
"message": "ไลบรารี"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "หน้าเริ่มต้น"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "ปิดป้ายชื่อบนศีรษะของผู้เล่นในหน้าแสดงสกิน"
|
||||
"message": "ซ่อนป้ายชื่อบนศีรษะของผู้เล่นในหน้าแสดงสกิน"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "ซ่อนป้ายชื่อ"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "การแสดงผลโลกล่าสุดที่ผู้เล่นเล่นในส่วน \"กระโดดกลับเข้าไป\" ในหน้าหลัก"
|
||||
"message": "แสดงโลกที่เล่นล่าสุดในส่วน \"เล่นต่อ\" บนหน้าหลัก"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "กระโดดกลับเข้าไปในโลก"
|
||||
"message": "เล่นต่อ"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "ย่อลันเชอร์เมื่อกระบวนการการรัน Minecraft เริ่มต้นแล้ว"
|
||||
@@ -78,13 +87,19 @@
|
||||
"message": "ย่อลันเชอร์"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "ใช้ระบบกรอบหน้าต่าง (ต้องการการรีสตาร์ทโปรแกรมใหม่)"
|
||||
"message": "ใช้กรอบหน้าต่างระบบ (ต้องรีสตาร์ทแอป)"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "ของตกแต่งดั้งเดิม"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "เลือกตัวเลิือก"
|
||||
"message": "เลือกตัวเลือก"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "แสดงเวลาที่คุณใช้เล่นในแต่ละอินสแตนซ์"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "แสดงระยะเวลาที่เล่น"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "เปิดใช้งานตัวเลือกในการเลือกย่อหรือขยายแถบด้านข้าง"
|
||||
@@ -104,6 +119,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "ไม่สามารถเชื่อมต่อถึงเซิร์ฟเวอร์ได้"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "เพิ่มเซิร์ฟเวอร์ลงในอินสแตนซ์"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "เพิ่มลงในอินสแตนซ์"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "เพิ่มลงในโปรแกรม"
|
||||
},
|
||||
@@ -116,6 +137,9 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "เพิ่มแล้ว"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "กลับสู่อินสแตนซ์"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "สำรวจเนื้อหา"
|
||||
},
|
||||
@@ -128,9 +152,15 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "แพ็กม็อด"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "การเพิ่มเนื้อหาอาจทำให้ใช้งานร่วมกันไม่ได้เมื่อเข้าร่วมเซิร์ฟเวอร์ เนื้อหาที่เพิ่มเข้ามาจะหายไปเมื่อคุณอัปเดตอินสแตนซ์ของเซิร์ฟเวอร์"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "กำลังติดตั้ง"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "ไม่มีเวอร์ชันที่เข้ากันได้ {compatibilityLabel} โปรดเลือกเวอร์ชันที่จะติดตั้ง ไฟล์ที่จำเป็นจะไม่ถูกติดตั้งโดยอัตโนมัติ"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -260,6 +290,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "ติดตั้ง"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# ม็อด}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "มอดแพ็กที่จำเป็น"
|
||||
},
|
||||
@@ -284,6 +317,15 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "จำเป็นต้องอัปเดตเพื่อเล่น {name} กรุณาอัปเดตเป็นเวอร์ชันล่าสุดเพื่อเปิดเกม"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "โปรเจคนี้ถูกติดตั้งแล้ว"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "กลับไปที่หน้าค้นหา"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "ติดตั้งเนื้อหาลงสู่อินสแตนซ์"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "โหมดนักพัฒนาเปิดอยู่"
|
||||
},
|
||||
@@ -308,6 +350,150 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "การจัดการทรัพยากร"
|
||||
},
|
||||
"app.skins.add-button": {
|
||||
"message": "เพิ่มสกิน"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "ลากและวาง"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "นำไปใช้"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "ลบสกิน"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"message": "การดำเนินการนี้จะลบสกินที่เลือกอย่างถาวร การกระทำนี้ไม่สามารถย้อนกลับได้"
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "คุณแน่ใจหรือว่าต้องการที่จะลบสกินนี้?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "ไม่สามารถอ่านไฟล์ที่ถูกลากและวางได้"
|
||||
},
|
||||
"app.skins.dropped-file-error.title": {
|
||||
"message": "ข้อผิดพลาดในการประมวลผลไฟล์"
|
||||
},
|
||||
"app.skins.edit-button": {
|
||||
"message": "แก้ไขสกิน"
|
||||
},
|
||||
"app.skins.modal.add-skin-button": {
|
||||
"message": "เพิ่มสกิน"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "การเพิ่มสกิน"
|
||||
},
|
||||
"app.skins.modal.arm-style-section": {
|
||||
"message": "สไตล์แขน"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "เพรียว"
|
||||
},
|
||||
"app.skins.modal.arm-style-wide": {
|
||||
"message": "กว้าง"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "ผ้าคลุม"
|
||||
},
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "ผ้าคลุม"
|
||||
},
|
||||
"app.skins.modal.edit-title": {
|
||||
"message": "แก้ไขสกิน"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "แก้ไขสกินก่อน!"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "ไม่มีผ้าคลุม"
|
||||
},
|
||||
"app.skins.modal.none-cape-option": {
|
||||
"message": "ไม่มี"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "เปลี่ยนเท็กซ์เจอร์"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "บันทึกสกิน"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "กำลังบันทึก"
|
||||
},
|
||||
"app.skins.modal.texture-section": {
|
||||
"message": "เท็กซ์เจอร์"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "อัพโหลดสกินก่อน!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "แก้ไขสกิน"
|
||||
},
|
||||
"app.skins.previewing-badge": {
|
||||
"message": "การแสดงตัวอย่าง"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "คุณได้เปลี่ยนสกินบ่อยเกินไป เซิร์ฟเวอร์ของ Mojang ได้บล็อกคำขอเพิ่มเติมชั่วคราว โปรดรอสักครู่ก่อนลองอีกครั้ง"
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "ลดความถี่ลง!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "ไม่สามารถบันทึกลำดับสกินของคุณได้"
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "ไม่สามารถจัดเรียงสกินได้"
|
||||
},
|
||||
"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": "สกินเริ่มต้น"
|
||||
},
|
||||
"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": "คุณได้รับสกินเหล่านี้จากการร่วมบริจาคให้กับกิจกรรมระดมทุน Modrinth Pride ในช่วงเดือน Pride Month"
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "Mounts of Mayhem"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "สกินที่บันทึกไว้"
|
||||
},
|
||||
"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": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "ลงชื่อเข้าใช้"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "โปรดลงชื่อเข้าใช้บัญชี Minecraft ของคุณเพื่อใช้งานฟีเจอร์จัดการสกินของแอป Modrinth"
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "บอท Modrinth ที่กำลังตื่นเต้น"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "โปรดลงชื่อเข้าใช้"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "ดาวน์โหลด Modrinth v{version} สำเร็จแล้ว เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
|
||||
},
|
||||
@@ -326,9 +512,6 @@
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "การดาวน์โหลดเสร็จสมบูรณ์"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "โหลดใหม่"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "มีการอัปเดตใหม่"
|
||||
},
|
||||
@@ -359,6 +542,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "เพิ่มเพื่อน"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "คำขอเป็นเพื่อน {count} {count, plural, other {คน}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "ส่งคำขอเป็นเพื่อน"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} indiriliyor"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Güncelleme indiriliyor"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "İndirmeler"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Birincil kurulum"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Güncellemek için yeniden yükle"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Daha fazla çalışan kurulum göster"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Kurulumu durdur"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Güncelleme"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Şu anda indirilienleri göster"
|
||||
},
|
||||
@@ -45,7 +54,7 @@
|
||||
"message": "Modrinth App için tercih ettiğiniz temayı seçin."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema"
|
||||
"message": "Tema Rengi"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Launcher açıldığında varsayılan olarak açılacak sayfayı seçin."
|
||||
@@ -135,7 +144,7 @@
|
||||
"message": "İçerik keşfet"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Sunucu keşfet"
|
||||
"message": "Sunucuları keşfet"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Zaten eklenmiş sunucuları gizle"
|
||||
@@ -428,6 +437,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Yavaşla!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Skinini kaydedemedik."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Skinler sıralanamadı"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "İnşaatçılar & Biyomlar"
|
||||
},
|
||||
@@ -501,7 +516,7 @@
|
||||
"message": "İndirme tamamlandı"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Yeniden başlat"
|
||||
"message": "Güncellemek için yeniden yükle"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Güncelleme mevcut"
|
||||
@@ -533,6 +548,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Bir arkadaş ekle"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} arkadaşlık isteği"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Arkadaşlık isteği gönder"
|
||||
},
|
||||
@@ -698,6 +716,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Ad"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Güncelleme kanalı"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alfa"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Release, beta ve alpha versiyonları mevcut güncellemeler olarak gösterilecek."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Release ve beta versiyonları mevcut güncellemeler olarak gösterilecek."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Yayın"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Yalnızca release versiyonları mevcut güncellemeler olarak gösterilecek."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Güncelleme kanalını seç"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Başlatma kancaları"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Завантаження Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Завантаження оновлення"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Завантаження"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Основний профіль"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Перезавантаження для оновлення"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Показати більше запущених профілів"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Зупинити профіль"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Оновлення"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Переглянути активні завантаження"
|
||||
},
|
||||
@@ -149,6 +158,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Установлення"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Жодна доступна версія не сумісна з {compatibilityLabel}. Виберіть версію, щоб усе одно її встановити. Залежності не встановлюватимуться автоматично."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -195,7 +207,7 @@
|
||||
"message": "Видалити профіль"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "Ця збірка вже встановлена в профілі <bold>«{instanceName}»</bold>. Ви впевнені, що хочете її скопіювати?"
|
||||
"message": "Ця збірка вже встановлена в профілі <bold>«{instanceName}»</bold>. Дійсно бажаєте її клонувати?"
|
||||
},
|
||||
"app.instance.modpack-already-installed.create": {
|
||||
"message": "Створити"
|
||||
@@ -269,6 +281,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Установити"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# мод} few {# мода} other {# модів}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Потрібна збірка"
|
||||
},
|
||||
@@ -285,13 +300,13 @@
|
||||
"message": "Дивитися вміст"
|
||||
},
|
||||
"app.modal.update-to-play.header": {
|
||||
"message": "Оновлення перед грою"
|
||||
"message": "Оновлення для гри"
|
||||
},
|
||||
"app.modal.update-to-play.update-required": {
|
||||
"message": "Необхідне оновлення"
|
||||
},
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "«{name}» потребує оновлення, щоб грати. Будь ласка, оновіть гру до останньої версії, щоб запустити її."
|
||||
"message": "«{name}» потребує оновлення, щоб грати. Будь ласка, оновіть до останньої версії, щоб запустити гру."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Проєкт уже встановлено"
|
||||
@@ -330,7 +345,7 @@
|
||||
"message": "Додати скін"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "Перетягніть і вставте"
|
||||
"message": "Перетягніть сюди"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Застосувати"
|
||||
@@ -342,7 +357,7 @@
|
||||
"message": "Це видалить вибраний скін назавжди. Ця дія не може бути скасована."
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "Чи ви впевнені що хочете видалити цей скін?"
|
||||
"message": "Ви дійсно бажаєте видалити цей скін?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Неможливо відкрити вибраний файл."
|
||||
@@ -378,19 +393,19 @@
|
||||
"message": "Редагування скіна"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "Відредагуйте скін для початку!"
|
||||
"message": "Спершу відредагуйте скін!"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "Без плаща"
|
||||
},
|
||||
"app.skins.modal.none-cape-option": {
|
||||
"message": "Без"
|
||||
"message": "Немає"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "Замінити текстуру"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "Зберегти вигляд"
|
||||
"message": "Зберегти скін"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "Збереження…"
|
||||
@@ -399,7 +414,7 @@
|
||||
"message": "Текстура"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "Завантажте скін для початку!"
|
||||
"message": "Спершу завантажте скін!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "Редагувати скін"
|
||||
@@ -408,10 +423,16 @@
|
||||
"message": "Попередній перегляд"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "Ви змінюєте свій скін дуже часто. Сервери Mojang тимчасово заблокували подальші запити. Зачекайте трохи перш ніж спробувати знову."
|
||||
"message": "Ви змінюєте скін занадто часто. Сервери Mojang тимчасово заблокували подальші запити. Зачекайте трохи, перш ніж спробувати знову."
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Шановний!"
|
||||
"message": "Повільніше!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Ваше замовлення скінів не вдалося зберегти."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Не вдалося змінити порядок скінів"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
@@ -468,13 +489,13 @@
|
||||
"message": "Вибір скіну"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App v{version} завантажено. Перезапустіть зараз, щоб оновити його, або це відбудеться автоматично після закриття Modrinth App."
|
||||
"message": "Modrinth App v{version} завантажено. Перезапустіть, щоб оновити зараз, або це відбудеться автоматично після закриття застосунку."
|
||||
},
|
||||
"app.update-popup.body.linux": {
|
||||
"message": "Modrinth App v{version} вже доступно. Скористайтеся вашим менеджером пакетів, щоб отримати найновіші функції та виправлення!"
|
||||
"message": "Modrinth App v{version} вже доступно. Скористайтеся менеджером пакетів, щоб отримати найновіші функції та виправлення!"
|
||||
},
|
||||
"app.update-popup.body.metered": {
|
||||
"message": "Modrinth App v{version} доступний для завантаження! Оскільки ви на лімітному з’єднанні, ми не завантажили його автоматично."
|
||||
"message": "Modrinth App v{version} вже доступно! Оскільки ви використовуєте лімітне підключення, ми не завантажили його автоматично."
|
||||
},
|
||||
"app.update-popup.changelog": {
|
||||
"message": "Журнал змін"
|
||||
@@ -486,7 +507,7 @@
|
||||
"message": "Завантаження завершено"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Перезавантажити"
|
||||
"message": "Перезавантаження для оновлення"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Доступне оновлення"
|
||||
@@ -510,7 +531,7 @@
|
||||
"message": "Ще не зіграно"
|
||||
},
|
||||
"app.world.world-item.offline": {
|
||||
"message": "Поза мережею"
|
||||
"message": "Не в мережі"
|
||||
},
|
||||
"app.world.world-item.players-online": {
|
||||
"message": "{count} у мережі"
|
||||
@@ -518,6 +539,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Додати друга"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} запит{count, plural, one { у} few {и в} many {ів у} other {у в}} друзі"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Надіслати запит у друзі"
|
||||
},
|
||||
@@ -683,6 +707,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Назва"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Випуск оновлення"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Альфа"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Випуски, бета- та альфа-версії будуть показуватися як доступні оновлення."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Бета"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Випуски та бета-версії будуть показуватися як доступні оновлення."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Випуск"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Тільки випуски версії будуть показуватися як доступні оновлення."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Оберіть випуск оновлення"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Гуки запуску"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Đang tải xuống Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "Đang tải xuống bản cập nhật"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Tải xuống"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Bản instance chính"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Tải lại để cập nhật"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Hiện thêm các phiên bản đang chạy"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Dừng instance"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "Cập nhật"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Xem các lượt tải xuống"
|
||||
},
|
||||
@@ -149,6 +158,9 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "Đang cài đặt"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "Không có phiên bản nào phù hợp với {compatibilityLabel}. Nếu vẫn chọn một phiên bản để cài đặt, các gói phụ thuộc sẽ không được tải tự động."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
@@ -278,6 +290,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Tải xuống"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# mod}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Yêu cầu modpack"
|
||||
},
|
||||
@@ -422,6 +437,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Bình tĩnh lại!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Không thể lưu thứ tự skin của bạn."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Sắp xếp lại skin thất bại"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
@@ -495,7 +516,7 @@
|
||||
"message": "Cài đặt thành công"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Khởi động lại"
|
||||
"message": "Tải lại để cập nhật"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Cập nhật mới hiện đang khả dụng"
|
||||
@@ -527,6 +548,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Thêm một người bạn"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, other {yêu cầu kết bạn}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Gửi yêu cầu kết bạn"
|
||||
},
|
||||
@@ -692,6 +716,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Tên"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "Kênh cập nhật"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Sơ khai"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "Các phiên bản chính thức, thử nghiệm và sơ khai sẽ hiển thị trong danh sách cập nhật có sẵn."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Thử nghiệm"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "Các phiên bản chính thức và thử nghiệm sẽ hiển thị trong danh sách cập nhật có sẵn."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "Chính thức"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "Chỉ phiên bản chính thức sẽ hiển thị trong danh sách cập nhật có sẵn."
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "Chọn kênh cập nhật"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Khởi chạy Hooks"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "正在下载 Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "下载更新中"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "下载"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "主实例"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "重新加载以更新"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "显示更多运行中的实例"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "停止实例"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "更新"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "查看正在下载内容"
|
||||
},
|
||||
@@ -63,7 +72,7 @@
|
||||
"message": "在皮肤页面中禁用你玩家头顶上的名牌。"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "隐藏名称标签"
|
||||
"message": "隐藏名牌"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "在主页的“快速回到”部分包含最近的世界。"
|
||||
@@ -111,7 +120,7 @@
|
||||
"message": "无法连接到身份验证服务器"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "将服务器添加到实例"
|
||||
"message": "正在添加服务器到此实例"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "添加到实例"
|
||||
@@ -144,7 +153,7 @@
|
||||
"message": "整合包"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "加入服务器时,添加内容可能会破坏兼容性。当更新服务器实例内容时任何添加的内容也将丢失。"
|
||||
"message": "加入服务器时,添加内容可能会破坏兼容性。当更新服务器实例内容时,任何添加的内容也将丢失。"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "安装中"
|
||||
@@ -272,6 +281,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "安装"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count} 个模组"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "需求整合包"
|
||||
},
|
||||
@@ -416,6 +428,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "慢点!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "未能保存皮肤顺序。"
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "皮肤排序失败"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "建造者与生物群系"
|
||||
},
|
||||
@@ -489,7 +507,7 @@
|
||||
"message": "下载完成"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "重新启动"
|
||||
"message": "重新加载以更新"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "有可用的更新"
|
||||
@@ -521,6 +539,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "添加好友"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} 个好友请求"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "发送好友请求"
|
||||
},
|
||||
@@ -686,6 +707,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "名称"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "更新渠道"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha 测试版"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "正式版,beta 和 alpha 测试版会被显示为可用更新。"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta 测试版"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "正式版和 beta 测试版会被显示为可用更新。"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "正式版"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "只有正式版会被显示为可用更新。"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "选择更新渠道"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "启动 Hooks"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "正在下載 Java {version}"
|
||||
},
|
||||
"app.action-bar.downloading-update": {
|
||||
"message": "正在下載更新"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "下載"
|
||||
},
|
||||
@@ -20,12 +23,18 @@
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "主要實例"
|
||||
},
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "重新載入即可更新"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "顯示更多執行中的實例"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "停止實例"
|
||||
},
|
||||
"app.action-bar.update": {
|
||||
"message": "更新"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "查看正在下載的項目"
|
||||
},
|
||||
@@ -33,10 +42,10 @@
|
||||
"message": "查看實例"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "查看紀錄檔"
|
||||
"message": "查看紀錄"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "啟用進階繪製(如模糊效果);若無硬體加速可能會導致效能問題。"
|
||||
"message": "啟用進階繪製(如模糊效果),若無硬體加速可能會導致效能問題。"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "進階繪製"
|
||||
@@ -78,19 +87,19 @@
|
||||
"message": "最小化啟動器"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "使用系統視窗外框(需要重新啟動應用程式)。"
|
||||
"message": "使用系統內建的視窗外框(需要重啟應用程式)。"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "原生裝飾"
|
||||
"message": "原生視窗"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "選擇選項"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "顯示您在該實例的遊玩時間"
|
||||
"message": "顯示你在該實例的遊玩時數。"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "顯示遊玩時間"
|
||||
"message": "顯示遊玩時數"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "啟用切換側邊欄的功能。"
|
||||
@@ -149,14 +158,17 @@
|
||||
"app.browse.server.installing": {
|
||||
"message": "安裝中"
|
||||
},
|
||||
"app.content-install.no-compatible-versions": {
|
||||
"message": "沒有符合 {compatibilityLabel} 的可用版本。請選擇一個版本強制安裝。不會自動安裝相依模組。"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "正在安裝模組包……"
|
||||
"message": "正在安裝模組包..."
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "輸入模組包描述……"
|
||||
"message": "輸入模組包描述..."
|
||||
},
|
||||
"app.export-modal.export-button": {
|
||||
"message": "匯出"
|
||||
@@ -255,7 +267,7 @@
|
||||
"message": "確定要移除「{name}」嗎?"
|
||||
},
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "搜尋 {count} 個世界……"
|
||||
"message": "搜尋 {count} 個世界..."
|
||||
},
|
||||
"app.instance.worlds.this-server": {
|
||||
"message": "這個伺服器"
|
||||
@@ -269,6 +281,9 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "安裝"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# 個模組}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "所需模組包"
|
||||
},
|
||||
@@ -327,25 +342,46 @@
|
||||
"message": "資源管理"
|
||||
},
|
||||
"app.skins.add-button": {
|
||||
"message": "新增皮膚"
|
||||
"message": "新增外觀"
|
||||
},
|
||||
"app.skins.add-button.drag-and-drop": {
|
||||
"message": "拖曳"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "確認"
|
||||
"message": "套用"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "刪除皮膚"
|
||||
"message": "刪除外觀"
|
||||
},
|
||||
"app.skins.delete-modal.description": {
|
||||
"message": "這會永久刪除所選的外觀,這個動作無法復原。"
|
||||
},
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "你確定要刪除這個皮膚嗎"
|
||||
"message": "你確定要刪除這個外觀嗎?"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "無法讀取拖曳的檔案。"
|
||||
},
|
||||
"app.skins.dropped-file-error.title": {
|
||||
"message": "處理檔案時發生錯誤"
|
||||
},
|
||||
"app.skins.edit-button": {
|
||||
"message": "編輯皮膚"
|
||||
"message": "編輯外觀"
|
||||
},
|
||||
"app.skins.modal.add-skin-button": {
|
||||
"message": "新增皮膚"
|
||||
"message": "新增外觀"
|
||||
},
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "正在新增皮膚"
|
||||
"message": "新增外觀"
|
||||
},
|
||||
"app.skins.modal.arm-style-section": {
|
||||
"message": "手臂身形"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "苗條"
|
||||
},
|
||||
"app.skins.modal.arm-style-wide": {
|
||||
"message": "寬"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "披風"
|
||||
@@ -354,7 +390,10 @@
|
||||
"message": "披風"
|
||||
},
|
||||
"app.skins.modal.edit-title": {
|
||||
"message": "編輯皮膚"
|
||||
"message": "編輯外觀"
|
||||
},
|
||||
"app.skins.modal.make-edit-first-tooltip": {
|
||||
"message": "請先對外觀進行編輯!"
|
||||
},
|
||||
"app.skins.modal.no-cape-tooltip": {
|
||||
"message": "無披風"
|
||||
@@ -363,46 +402,91 @@
|
||||
"message": "無"
|
||||
},
|
||||
"app.skins.modal.replace-texture-button": {
|
||||
"message": "覆蓋材質包"
|
||||
"message": "取代材質"
|
||||
},
|
||||
"app.skins.modal.save-skin-button": {
|
||||
"message": "儲存皮膚"
|
||||
"message": "儲存外觀"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "正在儲存"
|
||||
"message": "正在儲存..."
|
||||
},
|
||||
"app.skins.modal.texture-section": {
|
||||
"message": "材質包"
|
||||
"message": "材質"
|
||||
},
|
||||
"app.skins.modal.upload-skin-first-tooltip": {
|
||||
"message": "馬上新增一個皮膚"
|
||||
"message": "請先上傳外觀檔案!"
|
||||
},
|
||||
"app.skins.preview.edit-button": {
|
||||
"message": "編輯皮膚"
|
||||
"message": "編輯外觀"
|
||||
},
|
||||
"app.skins.previewing-badge": {
|
||||
"message": "正在預覽"
|
||||
"message": "預覽"
|
||||
},
|
||||
"app.skins.rate-limit.text": {
|
||||
"message": "你變更外觀的頻率太過頻繁。Mojang 伺服器已暫時封鎖後續請求,請稍等片刻,然後再試一次。"
|
||||
},
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "慢一點"
|
||||
"message": "慢一點!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "無法儲存你的外觀排序。"
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "重新排序外觀失敗"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "建造者&生態域"
|
||||
},
|
||||
"app.skins.section.chase-the-skies": {
|
||||
"message": "追逐天空"
|
||||
},
|
||||
"app.skins.section.default-skins": {
|
||||
"message": "預設皮膚"
|
||||
"message": "預設外觀"
|
||||
},
|
||||
"app.skins.section.minecon-earth-2017": {
|
||||
"message": "MINECON Earth 2017"
|
||||
},
|
||||
"app.skins.section.modrinth": {
|
||||
"message": "Modrinth"
|
||||
},
|
||||
"app.skins.section.modrinth-pride": {
|
||||
"message": "Modrinth 驕傲"
|
||||
},
|
||||
"app.skins.section.modrinth-pride.tooltip": {
|
||||
"message": "這些外觀是你在驕傲月期間捐款給 Modrinth 驕傲月籌款活動所獲得的。"
|
||||
},
|
||||
"app.skins.section.mounts-of-mayhem": {
|
||||
"message": "群騎紛爭"
|
||||
},
|
||||
"app.skins.section.saved-skins": {
|
||||
"message": "皮膚已儲存"
|
||||
"message": "已儲存外觀"
|
||||
},
|
||||
"app.skins.section.striding-hero": {
|
||||
"message": "Striding Hero"
|
||||
},
|
||||
"app.skins.section.the-copper-age": {
|
||||
"message": "銅器時代"
|
||||
},
|
||||
"app.skins.section.the-garden-awakens": {
|
||||
"message": "蒼園覺醒"
|
||||
},
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "小鬼當家"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "登入"
|
||||
},
|
||||
"app.skins.sign-in.description": {
|
||||
"message": "請登入你的 Minecraft 帳號以使用 Modrinth App 的外觀管理功能。"
|
||||
},
|
||||
"app.skins.sign-in.rinthbot-alt": {
|
||||
"message": "興奮的 Modrinth 機器人"
|
||||
},
|
||||
"app.skins.sign-in.title": {
|
||||
"message": "請登入"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "皮膚選擇"
|
||||
"message": "外觀選擇工具"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Modrinth App v{version} 已完成下載!立即重新載入以更新,或在關閉 Modrinth App 時自動更新。"
|
||||
@@ -423,7 +507,7 @@
|
||||
"message": "下載完成"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "重新載入"
|
||||
"message": "重新載入即可更新"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "有可用的更新"
|
||||
@@ -455,6 +539,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "新增好友"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} 個好友{count, plural, other {邀請}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "送出好友邀請"
|
||||
},
|
||||
@@ -620,6 +707,30 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "名稱"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel": {
|
||||
"message": "更新通道"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha 版"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha.description": {
|
||||
"message": "相容的正式版、Beta 版和 Alpha 版本都會顯示在可用更新中。"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta 版"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta.description": {
|
||||
"message": "相容的正式版和 Beta 版本都會顯示在可用更新中。"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release": {
|
||||
"message": "正式版"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.release.description": {
|
||||
"message": "僅有正式版會顯示在可用更新中。"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.select": {
|
||||
"message": "選擇更新通道"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "啟動掛勾"
|
||||
},
|
||||
@@ -681,10 +792,10 @@
|
||||
"message": "自訂記憶體分配"
|
||||
},
|
||||
"instance.settings.tabs.java.enter-environment-variables": {
|
||||
"message": "輸入環境變數……"
|
||||
"message": "輸入環境變數..."
|
||||
},
|
||||
"instance.settings.tabs.java.enter-java-arguments": {
|
||||
"message": "輸入 Java 參數……"
|
||||
"message": "輸入 Java 參數..."
|
||||
},
|
||||
"instance.settings.tabs.java.environment-variables": {
|
||||
"message": "環境變數"
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import type { LocationQuery } from 'vue-router'
|
||||
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
get_search_results_v3,
|
||||
get_version_many,
|
||||
} from '@/helpers/cache.js'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
|
||||
import {
|
||||
get as getInstance,
|
||||
@@ -181,6 +182,28 @@ watchServerContextChanges()
|
||||
|
||||
await initInstanceContext()
|
||||
|
||||
async function refreshInstalledProjectIds() {
|
||||
if (!route.query.i) return
|
||||
|
||||
if (route.query.from === 'worlds') {
|
||||
const worlds = await get_profile_worlds(route.query.i as string).catch(handleError)
|
||||
if (!worlds) return
|
||||
|
||||
const serverProjectIds = worlds
|
||||
.filter((w) => w.type === 'server' && 'project_id' in w && w.project_id)
|
||||
.map((w) => (w as { project_id: string }).project_id)
|
||||
debugLog('installedServerProjectIds loaded', { count: serverProjectIds.length })
|
||||
installedProjectIds.value = serverProjectIds
|
||||
return
|
||||
}
|
||||
|
||||
const ids = await getInstalledProjectIds(route.query.i as string).catch(handleError)
|
||||
if (!ids) return
|
||||
|
||||
debugLog('installedProjectIds loaded', { count: ids.length })
|
||||
installedProjectIds.value = ids
|
||||
}
|
||||
|
||||
async function initInstanceContext() {
|
||||
debugLog('initInstanceContext', {
|
||||
queryI: route.query.i,
|
||||
@@ -199,24 +222,7 @@ async function initInstanceContext() {
|
||||
gameVersion: instance.value?.game_version,
|
||||
})
|
||||
|
||||
if (route.query.from === 'worlds') {
|
||||
get_profile_worlds(route.query.i as string)
|
||||
.then((worlds) => {
|
||||
const serverProjectIds = worlds
|
||||
.filter((w) => w.type === 'server' && 'project_id' in w && w.project_id)
|
||||
.map((w) => (w as { project_id: string }).project_id)
|
||||
debugLog('installedServerProjectIds loaded', { count: serverProjectIds.length })
|
||||
installedProjectIds.value = serverProjectIds
|
||||
})
|
||||
.catch(handleError)
|
||||
} else {
|
||||
getInstalledProjectIds(route.query.i as string)
|
||||
.then((ids) => {
|
||||
debugLog('installedProjectIds loaded', { count: ids?.length })
|
||||
installedProjectIds.value = ids
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
await refreshInstalledProjectIds()
|
||||
|
||||
if (instance.value?.linked_data?.project_id) {
|
||||
debugLog('checking linked project for server status', instance.value.linked_data.project_id)
|
||||
@@ -805,10 +811,10 @@ function getCardActions(
|
||||
selectedInstall.versionId,
|
||||
instance.value ? instance.value.path : null,
|
||||
'SearchCard',
|
||||
(versionId) => {
|
||||
(versionId, installedProjectIds) => {
|
||||
setProjectInstalling(projectResult.project_id, false)
|
||||
if (versionId) {
|
||||
onSearchResultInstalled(projectResult.project_id)
|
||||
onSearchResultsInstalled(installedProjectIds ?? [projectResult.project_id])
|
||||
}
|
||||
},
|
||||
(profile) => {
|
||||
@@ -834,7 +840,19 @@ function onSearchResultInstalled(id: string) {
|
||||
markServerProjectInstalled(id)
|
||||
return
|
||||
}
|
||||
newlyInstalled.value.push(id)
|
||||
if (!newlyInstalled.value.includes(id)) {
|
||||
newlyInstalled.value = [...newlyInstalled.value, id]
|
||||
}
|
||||
}
|
||||
|
||||
function onSearchResultsInstalled(ids: string[]) {
|
||||
if (isServerContext.value) {
|
||||
for (const id of ids) {
|
||||
markServerProjectInstalled(id)
|
||||
}
|
||||
return
|
||||
}
|
||||
newlyInstalled.value = Array.from(new Set([...newlyInstalled.value, ...ids]))
|
||||
}
|
||||
|
||||
async function search(requestParams: string) {
|
||||
@@ -966,6 +984,38 @@ if (instance.value?.game_version) {
|
||||
|
||||
await searchState.refreshSearch()
|
||||
|
||||
type UnlistenFn = () => void
|
||||
|
||||
let isUnmounted = false
|
||||
let unlistenProfiles: UnlistenFn | null = null
|
||||
|
||||
onMounted(() => {
|
||||
profile_listener(async (event: { event: string; profile_path_id: string }) => {
|
||||
if (
|
||||
instance.value &&
|
||||
event.profile_path_id === instance.value.path &&
|
||||
event.event === 'synced'
|
||||
) {
|
||||
await refreshInstalledProjectIds()
|
||||
await searchState.refreshSearch()
|
||||
}
|
||||
})
|
||||
.then((unlisten) => {
|
||||
if (isUnmounted) {
|
||||
unlisten()
|
||||
return
|
||||
}
|
||||
|
||||
unlistenProfiles = unlisten
|
||||
})
|
||||
.catch(handleError)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
isUnmounted = true
|
||||
unlistenProfiles?.()
|
||||
})
|
||||
|
||||
function getProjectBrowseQuery() {
|
||||
if (!installContext.value) return undefined
|
||||
return {
|
||||
|
||||
@@ -90,6 +90,7 @@ import {
|
||||
useVIntl,
|
||||
versionChangesGameVersion,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
@@ -99,7 +100,7 @@ import { useRouter } from 'vue-router'
|
||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_versions, get_version } from '@/helpers/cache.js'
|
||||
import { get_project_versions, get_version, get_version_many } from '@/helpers/cache.js'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import { type InstanceContentData, loadInstanceContentData } from '@/helpers/instance-content'
|
||||
import {
|
||||
@@ -153,6 +154,7 @@ const { formatMessage } = useVIntl()
|
||||
const { handleError, addNotification } = injectNotificationManager()
|
||||
const { installingItems } = injectContentInstall()
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const debug = useDebugLogger('Mods:ContentUpdate')
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -212,6 +214,13 @@ const contentUpdaterModal = ref<InstanceType<typeof ContentUpdaterModal> | null>
|
||||
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal> | null>()
|
||||
const modpackUpdateConfirmModal = ref<InstanceType<typeof ConfirmModpackUpdateModal> | null>()
|
||||
|
||||
const modpackContentQueryKey = computed(() => ['linkedModpackContent', props.instance.path])
|
||||
const modpackContentQuery = useQuery({
|
||||
queryKey: modpackContentQueryKey,
|
||||
queryFn: () => get_linked_modpack_content(props.instance.path),
|
||||
enabled: computed(() => !!props.instance?.path && !!props.instance?.linked_data),
|
||||
})
|
||||
|
||||
// TODO: Extract content operation and updater modal state into composables; this page currently owns file mutations, dependency installs, busy flags, and version selection flow.
|
||||
const updatingProject = ref<ContentItem | null>(null)
|
||||
const updatingProjectVersions = ref<Labrinth.Versions.v2.Version[]>([])
|
||||
@@ -451,6 +460,63 @@ async function removeMod(mod: ContentItem) {
|
||||
}
|
||||
}
|
||||
|
||||
function isBreakingDependency(dependency: Labrinth.Versions.v2.Dependency) {
|
||||
return dependency.dependency_type === 'required' || dependency.dependency_type === 'embedded'
|
||||
}
|
||||
|
||||
function dependencyTargetsItem(dependency: Labrinth.Versions.v2.Dependency, item: ContentItem) {
|
||||
return (
|
||||
(!!dependency.project_id && dependency.project_id === item.project?.id) ||
|
||||
('version_id' in dependency &&
|
||||
!!dependency.version_id &&
|
||||
dependency.version_id === item.version?.id)
|
||||
)
|
||||
}
|
||||
|
||||
async function getDeleteDependencyWarning(items: ContentItem[]) {
|
||||
if (props.isServerInstance) return null
|
||||
|
||||
const deletingIds = new Set(items.map(getContentItemId))
|
||||
const remainingItems = projects.value.filter((item) => !deletingIds.has(getContentItemId(item)))
|
||||
const versionIds = [
|
||||
...new Set(remainingItems.map((item) => item.version?.id).filter((id): id is string => !!id)),
|
||||
]
|
||||
|
||||
if (versionIds.length === 0) return null
|
||||
|
||||
const versions = (await get_version_many(versionIds).catch((err) => {
|
||||
handleError(err as Error)
|
||||
return null
|
||||
})) as Labrinth.Versions.v2.Version[] | null
|
||||
|
||||
if (!versions) return null
|
||||
|
||||
const versionsById = new Map(versions.map((version) => [version.id, version]))
|
||||
|
||||
const dependents = remainingItems
|
||||
.map((candidate) => {
|
||||
const version = candidate.version?.id ? versionsById.get(candidate.version.id) : null
|
||||
if (!version) return null
|
||||
|
||||
const dependencies = items.filter((item) => {
|
||||
if (!item.project?.id && !item.version?.id) return false
|
||||
|
||||
return version.dependencies?.some(
|
||||
(dependency) =>
|
||||
isBreakingDependency(dependency) && dependencyTargetsItem(dependency, item),
|
||||
)
|
||||
})
|
||||
|
||||
return dependencies.length > 0 ? { item: candidate, dependencies } : null
|
||||
})
|
||||
.filter(
|
||||
(dependent): dependent is { item: ContentItem; dependencies: ContentItem[] } =>
|
||||
dependent !== null,
|
||||
)
|
||||
|
||||
return dependents.length > 0 ? { items, dependents } : null
|
||||
}
|
||||
|
||||
async function updateProject(mod: ContentItem) {
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
@@ -481,6 +547,7 @@ async function updateProject(mod: ContentItem) {
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
throw err
|
||||
} finally {
|
||||
await refreshContentState('must_revalidate')
|
||||
finishContentOperation(mod, operation)
|
||||
@@ -682,13 +749,19 @@ async function handleModpackContentBulkToggle(items: ContentItem[]) {
|
||||
async function handleModpackContent() {
|
||||
if (!props.instance?.path) return
|
||||
|
||||
if (modpackContentQuery.data.value !== undefined) {
|
||||
modpackContentModal.value?.show(modpackContentQuery.data.value)
|
||||
return
|
||||
}
|
||||
|
||||
modpackContentModal.value?.showLoading()
|
||||
|
||||
const contentItems = await get_linked_modpack_content(props.instance.path).catch(handleError)
|
||||
const { data, error } = await modpackContentQuery.refetch()
|
||||
|
||||
if (contentItems) {
|
||||
modpackContentModal.value?.show(contentItems)
|
||||
if (data !== undefined) {
|
||||
modpackContentModal.value?.show(data)
|
||||
} else {
|
||||
if (error) handleError(error)
|
||||
modpackContentModal.value?.hide()
|
||||
}
|
||||
}
|
||||
@@ -696,9 +769,12 @@ async function handleModpackContent() {
|
||||
async function refreshModpackContentItems(cacheBehaviour?: CacheBehaviour) {
|
||||
if (!props.instance?.path) return
|
||||
|
||||
const contentItems = await get_linked_modpack_content(props.instance.path, cacheBehaviour).catch(
|
||||
handleError,
|
||||
)
|
||||
const contentItems = await queryClient
|
||||
.fetchQuery({
|
||||
queryKey: modpackContentQueryKey.value,
|
||||
queryFn: () => get_linked_modpack_content(props.instance.path, cacheBehaviour),
|
||||
})
|
||||
.catch(handleError)
|
||||
|
||||
if (contentItems) {
|
||||
modpackContentModal.value?.setItems(contentItems)
|
||||
@@ -885,13 +961,15 @@ async function handleModalUpdate(
|
||||
} else if (updatingProject.value) {
|
||||
const mod = updatingProject.value
|
||||
|
||||
if (mod.has_update && mod.update_version_id === selectedVersion.id) {
|
||||
await updateProject(mod)
|
||||
} else {
|
||||
await switchProjectVersion(mod, selectedVersion)
|
||||
try {
|
||||
if (mod.has_update && mod.update_version_id === selectedVersion.id) {
|
||||
await updateProject(mod)
|
||||
} else {
|
||||
await switchProjectVersion(mod, selectedVersion)
|
||||
}
|
||||
} finally {
|
||||
resetUpdateState()
|
||||
}
|
||||
|
||||
resetUpdateState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1081,6 +1159,7 @@ provideContentManager({
|
||||
deleteItem: removeMod,
|
||||
bulkDeleteItems: (items: ContentItem[]) =>
|
||||
Promise.all(items.map((item) => removeMod(item))).then(() => {}),
|
||||
getDeleteDependencyWarning,
|
||||
refresh: () => initProjects('must_revalidate'),
|
||||
browse: handleBrowseContent,
|
||||
uploadFiles: handleUploadFiles,
|
||||
|
||||
@@ -406,6 +406,20 @@ function buildProjectHref(path, extraQuery = {}) {
|
||||
return qs ? `${path}?${qs}` : path
|
||||
}
|
||||
|
||||
function buildBrowseHref(path) {
|
||||
const params = new URLSearchParams()
|
||||
for (const [key, val] of Object.entries(route.query)) {
|
||||
if (key === 'b') continue
|
||||
if (Array.isArray(val)) {
|
||||
for (const v of val) params.append(key, v)
|
||||
} else if (val) {
|
||||
params.append(key, String(val))
|
||||
}
|
||||
}
|
||||
const qs = params.toString()
|
||||
return qs ? `${path}?${qs}` : path
|
||||
}
|
||||
|
||||
const projectDescriptionHref = computed(() => buildProjectHref(`/project/${route.params.id}`))
|
||||
const versionsHref = computed(() =>
|
||||
buildProjectHref(`/project/${route.params.id}/versions`, instanceFilters.value),
|
||||
@@ -416,7 +430,7 @@ const projectBrowseBackUrl = computed(() => {
|
||||
const browsePath = route.query.b
|
||||
if (typeof browsePath === 'string' && browsePath.startsWith('/browse/')) return browsePath
|
||||
const type = data.value?.project_type ? `${data.value.project_type}` : 'mod'
|
||||
return `/browse/${type}`
|
||||
return buildBrowseHref(`/browse/${type}`)
|
||||
})
|
||||
|
||||
const projectInstallContext = computed(() => {
|
||||
@@ -725,10 +739,11 @@ async function install(version) {
|
||||
version,
|
||||
instance.value ? instance.value.path : null,
|
||||
'ProjectPage',
|
||||
(version) => {
|
||||
(version, installedProjectIds) => {
|
||||
installing.value = false
|
||||
|
||||
if (instance.value && version) {
|
||||
const installedIds = installedProjectIds ?? [data.value.id]
|
||||
if (instance.value && version && installedIds.includes(data.value.id)) {
|
||||
installed.value = true
|
||||
installedVersion.value = version
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:current-title="version.name"
|
||||
:link-stack="[
|
||||
{
|
||||
href: `/project/${route.params.id}/versions`,
|
||||
href: buildProjectHref(`/project/${route.params.id}/versions`),
|
||||
label: 'Versions',
|
||||
},
|
||||
]"
|
||||
@@ -249,6 +249,19 @@ const author = computed(() =>
|
||||
|
||||
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) params.append(key, v)
|
||||
} else if (val) {
|
||||
params.append(key, String(val))
|
||||
}
|
||||
}
|
||||
const qs = params.toString()
|
||||
return qs ? `${path}?${qs}` : path
|
||||
}
|
||||
|
||||
async function refreshDisplayDependencies() {
|
||||
const projectIds = new Set()
|
||||
const versionIds = new Set()
|
||||
@@ -282,7 +295,7 @@ async function refreshDisplayDependencies() {
|
||||
icon: project?.icon_url,
|
||||
title: project?.title || project?.name,
|
||||
subtitle: `Version ${version.version_number} is ${dependency.dependency_type}`,
|
||||
link: `/project/${project.slug}/version/${version.id}`,
|
||||
link: buildProjectHref(`/project/${project.slug}/version/${version.id}`),
|
||||
}
|
||||
} else {
|
||||
const project = dependencies.projects.find((obj) => obj.id === dependency.project_id)
|
||||
@@ -292,7 +305,7 @@ async function refreshDisplayDependencies() {
|
||||
icon: project?.icon_url,
|
||||
title: project?.title || project?.name,
|
||||
subtitle: `${dependency.dependency_type}`,
|
||||
link: `/project/${project.slug}`,
|
||||
link: buildProjectHref(`/project/${project.slug}`),
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:game-versions="gameVersions"
|
||||
:versions="versions"
|
||||
:project="project"
|
||||
:version-link="(version) => `/project/${project.id}/version/${version.id}`"
|
||||
:version-link="(version) => buildProjectHref(`/project/${project.id}/version/${version.id}`)"
|
||||
>
|
||||
<template #actions="{ version }">
|
||||
<ButtonStyled circular type="transparent">
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
ProjectPageVersions,
|
||||
} 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'
|
||||
@@ -109,6 +110,20 @@ defineProps({
|
||||
})
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const route = useRoute()
|
||||
|
||||
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) params.append(key, v)
|
||||
} else if (val) {
|
||||
params.append(key, String(val))
|
||||
}
|
||||
}
|
||||
const qs = params.toString()
|
||||
return qs ? `${path}?${qs}` : path
|
||||
}
|
||||
|
||||
const [loaders, gameVersions] = await Promise.all([
|
||||
get_loaders().catch(handleError).then(ref),
|
||||
|
||||
@@ -42,6 +42,8 @@ interface ModpackAlreadyInstalledModalRef {
|
||||
show: (instanceName: string, instancePath: string) => void
|
||||
}
|
||||
|
||||
export type ContentInstallCallback = (versionId?: string, installedProjectIds?: string[]) => void
|
||||
|
||||
const LOADER_ORDER = ['vanilla', 'fabric', 'quilt', 'neoforge', 'forge']
|
||||
const SUPPORTED_LOADERS: Set<string> = new Set(['vanilla', 'forge', 'fabric', 'quilt', 'neoforge'])
|
||||
const VANILLA_COMPATIBLE_LOADERS: Set<string> = new Set(['minecraft', 'datapack'])
|
||||
@@ -102,7 +104,7 @@ export interface ContentInstallContext {
|
||||
versionId?: string | null,
|
||||
instancePath?: string | null,
|
||||
source?: string,
|
||||
callback?: (versionId?: string) => void,
|
||||
callback?: ContentInstallCallback,
|
||||
createInstanceCallback?: (profile: string) => void,
|
||||
hints?: { preferredLoader?: string; preferredGameVersion?: string; showProjectInfo?: boolean },
|
||||
) => Promise<void>
|
||||
@@ -256,25 +258,25 @@ export function createContentInstall(opts: {
|
||||
let incompatibilityWarningModalRef: ModalRef | null = null
|
||||
let currentProject: Labrinth.Projects.v2.Project | null = null
|
||||
let currentVersions: Labrinth.Versions.v2.Version[] = []
|
||||
let currentCallback: (versionId?: string) => void = () => {}
|
||||
let currentCallback: ContentInstallCallback = () => {}
|
||||
let profileMap: Record<string, GameInstance> = {}
|
||||
let incompatibilityWarningInstance: GameInstance | null = null
|
||||
let incompatibilityWarningProject: Labrinth.Projects.v2.Project | null = null
|
||||
let incompatibilityWarningCallback: (versionId?: string) => void = () => {}
|
||||
let incompatibilityWarningCallback: ContentInstallCallback = () => {}
|
||||
let incompatibilityWarningInstalled = false
|
||||
|
||||
let pendingModpackInstall: {
|
||||
project: Labrinth.Projects.v2.Project
|
||||
version: string
|
||||
source: string
|
||||
callback: (versionId?: string) => void
|
||||
callback: ContentInstallCallback
|
||||
createInstanceCallback: (profile: string) => void
|
||||
} | null = null
|
||||
|
||||
async function showModInstallModal(
|
||||
project: Labrinth.Projects.v2.Project,
|
||||
versions: Labrinth.Versions.v2.Version[],
|
||||
onInstall: (versionId?: string) => void,
|
||||
onInstall: ContentInstallCallback,
|
||||
hints?: { preferredLoader?: string; preferredGameVersion?: string; showProjectInfo?: boolean },
|
||||
) {
|
||||
currentProject = project
|
||||
@@ -440,7 +442,7 @@ export function createContentInstall(opts: {
|
||||
if (versionId && storeInstance) {
|
||||
storeInstance.installed = true
|
||||
}
|
||||
currentCallback(versionId)
|
||||
currentCallback(versionId, versionId && currentProject ? [currentProject.id] : undefined)
|
||||
}
|
||||
await showIncompatibilityWarning(
|
||||
profile,
|
||||
@@ -487,7 +489,7 @@ export function createContentInstall(opts: {
|
||||
title: currentProject!.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
currentCallback(version.id)
|
||||
currentCallback(version.id, installedProjectIds)
|
||||
} catch (err) {
|
||||
if (storeInstance) storeInstance.installing = false
|
||||
opts.handleError(err)
|
||||
@@ -501,7 +503,7 @@ export function createContentInstall(opts: {
|
||||
project: Labrinth.Projects.v2.Project,
|
||||
versions: Labrinth.Versions.v2.Version[],
|
||||
version: Labrinth.Versions.v2.Version,
|
||||
callback: (versionId?: string) => void,
|
||||
callback: ContentInstallCallback,
|
||||
) {
|
||||
incompatibilityWarningInstance = instance
|
||||
incompatibilityWarningProject = project
|
||||
@@ -542,7 +544,7 @@ export function createContentInstall(opts: {
|
||||
|
||||
incompatibilityWarningInstalling.value = false
|
||||
incompatibilityWarningInstalled = true
|
||||
incompatibilityWarningCallback(version.id)
|
||||
incompatibilityWarningCallback(version.id, [incompatibilityWarningProject.id])
|
||||
incompatibilityWarningModalRef?.hide()
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
@@ -630,7 +632,7 @@ export function createContentInstall(opts: {
|
||||
versionId?: string | null,
|
||||
instancePath?: string | null,
|
||||
source: string = 'unknown',
|
||||
callback: (versionId?: string) => void = () => {},
|
||||
callback: ContentInstallCallback = () => {},
|
||||
createInstanceCallback: (profile: string) => void = () => {},
|
||||
hints?: { preferredLoader?: string; preferredGameVersion?: string; showProjectInfo?: boolean },
|
||||
) {
|
||||
@@ -714,7 +716,7 @@ export function createContentInstall(opts: {
|
||||
title: project.title,
|
||||
source,
|
||||
})
|
||||
callback(version.id)
|
||||
callback(version.id, installedProjectIds)
|
||||
} finally {
|
||||
removeInstallingItems(instancePath, installedProjectIds)
|
||||
}
|
||||
|
||||
@@ -432,6 +432,20 @@ components:
|
||||
primary_file:
|
||||
type: string
|
||||
description: The multipart field name of the primary file
|
||||
environment:
|
||||
type: string
|
||||
enum:
|
||||
- client_and_server
|
||||
- client_only
|
||||
- client_only_server_optional
|
||||
- singleplayer_only
|
||||
- server_only
|
||||
- server_only_client_optional
|
||||
- dedicated_server_only
|
||||
- client_or_server
|
||||
- client_or_server_prefers_both
|
||||
- unknown
|
||||
description: The environment that this version is for.
|
||||
required:
|
||||
- file_parts
|
||||
- project_id
|
||||
|
||||
@@ -14,6 +14,8 @@ import { I18nDebugPanel, LoadingBar, NotificationPanel } from '@modrinth/ui'
|
||||
|
||||
import { setupProviders } from '~/providers/setup.ts'
|
||||
|
||||
import { useAuth } from './composables/auth'
|
||||
|
||||
const auth = await useAuth()
|
||||
setupProviders(auth)
|
||||
</script>
|
||||
|
||||
@@ -756,16 +756,16 @@ svg.inline-svg {
|
||||
padding: var(--gap-16);
|
||||
|
||||
h2 {
|
||||
@apply font-semibold;
|
||||
font-size: var(--text-18);
|
||||
font-weight: var(--weight-extrabold);
|
||||
color: var(--color-contrast);
|
||||
line-height: initial;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply font-semibold;
|
||||
font-size: var(--text-16);
|
||||
font-weight: var(--weight-bold);
|
||||
color: var(--color-base);
|
||||
margin: 0;
|
||||
}
|
||||
@@ -812,6 +812,7 @@ svg.inline-svg {
|
||||
}
|
||||
|
||||
.details-list__item {
|
||||
@apply font-normal;
|
||||
@extend .iconified-list-item;
|
||||
|
||||
.details-list__item__text--style-secondary {
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
class="inline-flex items-center"
|
||||
>
|
||||
<button
|
||||
v-tooltip="legendEntry.projectName ?? ''"
|
||||
v-tooltip="legendEntry.tooltip ?? legendEntry.projectName ?? ''"
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1.5 px-2 py-0.5 text-sm !outline-0 transition-all focus-within:!outline-0 focus:!outline-0 focus-visible:!outline-0"
|
||||
:class="[
|
||||
|
||||
+4
-1
@@ -64,6 +64,7 @@ export function useAnalyticsChartLegend({
|
||||
id: dataset.projectId,
|
||||
name: dataset.label,
|
||||
projectName: dataset.projectName,
|
||||
tooltip: dataset.tooltip,
|
||||
color: dataset.borderColor,
|
||||
totalValue: getChartDatasetTotal(dataset),
|
||||
hidden: hiddenDatasetIds.value.has(dataset.projectId),
|
||||
@@ -118,6 +119,7 @@ export function useAnalyticsChartLegend({
|
||||
id: getPreviousPeriodDatasetId(entry.id),
|
||||
name: formatMessage(analyticsChartMessages.previousPeriodSuffix, { name: entry.name }),
|
||||
projectName: entry.projectName,
|
||||
tooltip: entry.tooltip,
|
||||
color: entry.color,
|
||||
totalValue: previousDataset ? getChartDatasetTotal(previousDataset) : 0,
|
||||
hidden: hiddenDatasetIds.value.has(getPreviousPeriodDatasetId(entry.id)),
|
||||
@@ -153,6 +155,7 @@ export function useAnalyticsChartLegend({
|
||||
name: dataset.label,
|
||||
}),
|
||||
projectName: dataset.projectName,
|
||||
tooltip: dataset.tooltip,
|
||||
data: previousData,
|
||||
borderColor: dataset.borderColor,
|
||||
backgroundColor: dataset.backgroundColor,
|
||||
@@ -296,7 +299,7 @@ export function useAnalyticsChartLegend({
|
||||
}
|
||||
|
||||
function getLegendEntryTooltip(legendEntry: AnalyticsChartLegendEntry) {
|
||||
return legendEntry.projectName ?? ''
|
||||
return legendEntry.tooltip ?? legendEntry.projectName ?? ''
|
||||
}
|
||||
|
||||
function isUnmonetizedLegendEntry(legendEntry: AnalyticsChartLegendEntry) {
|
||||
|
||||
+2
-1
@@ -97,7 +97,7 @@
|
||||
"
|
||||
/>
|
||||
<span
|
||||
v-tooltip="entry.projectName ?? ''"
|
||||
v-tooltip="entry.tooltip ?? entry.projectName ?? ''"
|
||||
class="min-w-0 truncate"
|
||||
:class="{
|
||||
'line-through': entry.hidden,
|
||||
@@ -145,6 +145,7 @@ export type AnalyticsChartTooltipEntry = {
|
||||
projectId: string
|
||||
name: string
|
||||
projectName?: string
|
||||
tooltip?: string
|
||||
color: string
|
||||
formattedValue: string
|
||||
hidden: boolean
|
||||
|
||||
+1
@@ -208,6 +208,7 @@ const hoverEntries = computed(() => {
|
||||
projectId: legendEntry.id,
|
||||
name: legendEntry.name,
|
||||
projectName: legendEntry.projectName,
|
||||
tooltip: legendEntry.tooltip,
|
||||
color: legendEntry.color,
|
||||
formattedValue: props.isRatioMode
|
||||
? `${ratioValue.toFixed(1)}%`
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ export type AnalyticsChartLegendEntry = {
|
||||
id: string
|
||||
name: string
|
||||
projectName?: string
|
||||
tooltip?: string
|
||||
color: string
|
||||
totalValue: number
|
||||
hidden: boolean
|
||||
|
||||
+192
-26
@@ -1,20 +1,24 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
|
||||
import type {
|
||||
AnalyticsBreakdownPreset,
|
||||
AnalyticsDashboardProject,
|
||||
AnalyticsDashboardStat,
|
||||
AnalyticsGroupByPreset,
|
||||
import {
|
||||
type AnalyticsBreakdownPreset,
|
||||
type AnalyticsDashboardProject,
|
||||
type AnalyticsDashboardStat,
|
||||
type AnalyticsGroupByPreset,
|
||||
type AnalyticsSelectedFilters,
|
||||
doesAnalyticsPointMatchNormalizedFilters,
|
||||
normalizeAnalyticsSelectedFilters,
|
||||
} from '~/providers/analytics/analytics'
|
||||
|
||||
import type { FormatMessage } from '../analytics-messages'
|
||||
import {
|
||||
analyticsChartMessages,
|
||||
analyticsMessages,
|
||||
analyticsStatCardMessages,
|
||||
formatAnalyticsDependentProjectFallbackLabel,
|
||||
formatAnalyticsDownloadReasonLabel,
|
||||
formatAnalyticsLoaderLabel,
|
||||
formatAnalyticsMonetizationLabel,
|
||||
type FormatMessage,
|
||||
} from '../analytics-messages'
|
||||
import {
|
||||
ALL_BREAKDOWN_VALUE,
|
||||
@@ -22,6 +26,8 @@ import {
|
||||
getAnalyticsBreakdownDatasetId,
|
||||
getAnalyticsBreakdownKey,
|
||||
getAnalyticsBreakdownValues,
|
||||
isNoDependentAnalyticsBreakdownValue,
|
||||
isUnknownAnalyticsBreakdownValue,
|
||||
UNKNOWN_BREAKDOWN_VALUE,
|
||||
} from '../breakdown'
|
||||
import { PREVIOUS_PERIOD_DATASET_ID_PREFIX } from './analytics-chart-constants'
|
||||
@@ -30,6 +36,7 @@ export type ChartDataset = {
|
||||
projectId: string
|
||||
label: string
|
||||
projectName?: string
|
||||
tooltip?: string
|
||||
data: number[]
|
||||
borderColor: string
|
||||
backgroundColor: string
|
||||
@@ -136,6 +143,7 @@ export function formatBreakdownLabel(
|
||||
breakdownValue: string,
|
||||
selectedBreakdown: AnalyticsBreakdownPreset,
|
||||
getVersionDisplayName: ((versionId: string) => string) | undefined,
|
||||
userNamesById: ReadonlyMap<string, string> | undefined,
|
||||
formatMessage: FormatMessage,
|
||||
): string {
|
||||
const normalizedValue = breakdownValue.trim()
|
||||
@@ -160,6 +168,15 @@ export function formatBreakdownLabel(
|
||||
if (selectedBreakdown === 'download_reason') {
|
||||
return formatAnalyticsDownloadReasonLabel(normalizedLowercaseValue, formatMessage)
|
||||
}
|
||||
if (selectedBreakdown === 'dependent_project_download') {
|
||||
if (isNoDependentAnalyticsBreakdownValue(breakdownValue)) {
|
||||
return formatMessage(analyticsMessages.noDependent)
|
||||
}
|
||||
return breakdownValue
|
||||
}
|
||||
if (selectedBreakdown === 'user_id') {
|
||||
return userNamesById?.get(breakdownValue) ?? breakdownValue
|
||||
}
|
||||
if (selectedBreakdown === 'version_id') {
|
||||
return getVersionDisplayName?.(breakdownValue) ?? breakdownValue
|
||||
}
|
||||
@@ -174,19 +191,34 @@ export function formatBreakdownLabels(
|
||||
breakdownValues: readonly string[],
|
||||
selectedBreakdowns: readonly AnalyticsBreakdownPreset[],
|
||||
getVersionDisplayName: ((versionId: string) => string) | undefined,
|
||||
userNamesById: ReadonlyMap<string, string> | undefined,
|
||||
formatMessage: FormatMessage,
|
||||
): string {
|
||||
const normalizedBreakdowns = selectedBreakdowns.filter((breakdown) => breakdown !== 'none')
|
||||
const downloadReasonBreakdownIndex = normalizedBreakdowns.indexOf('download_reason')
|
||||
|
||||
return collapseRepeatedUnknownBreakdownLabels(
|
||||
selectedBreakdowns
|
||||
.filter((breakdown) => breakdown !== 'none')
|
||||
.map((breakdown, index) =>
|
||||
formatBreakdownLabel(
|
||||
breakdownValues[index] ?? '',
|
||||
breakdown,
|
||||
getVersionDisplayName,
|
||||
normalizedBreakdowns.map((breakdown, index) => {
|
||||
const breakdownValue = breakdownValues[index] ?? ''
|
||||
if (
|
||||
breakdown === 'dependent_project_download' &&
|
||||
downloadReasonBreakdownIndex !== -1 &&
|
||||
isUnknownAnalyticsBreakdownValue(breakdownValue)
|
||||
) {
|
||||
return formatAnalyticsDependentProjectFallbackLabel(
|
||||
breakdownValues[downloadReasonBreakdownIndex],
|
||||
formatMessage,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return formatBreakdownLabel(
|
||||
breakdownValue,
|
||||
breakdown,
|
||||
getVersionDisplayName,
|
||||
userNamesById,
|
||||
formatMessage,
|
||||
)
|
||||
}),
|
||||
formatMessage,
|
||||
).join(COMBINED_BREAKDOWN_LABEL_SEPARATOR)
|
||||
}
|
||||
@@ -258,6 +290,33 @@ type PaletteRankEntry = {
|
||||
total: number
|
||||
}
|
||||
|
||||
function formatDatasetTooltip(projectName: string | undefined): string | undefined {
|
||||
return projectName
|
||||
}
|
||||
|
||||
function formatDependentProjectDatasetTooltip(
|
||||
versionName: string | undefined,
|
||||
dependentProjectName: string | undefined,
|
||||
dependencyProjectNames: readonly string[],
|
||||
formatMessage: FormatMessage,
|
||||
): string | undefined {
|
||||
if (dependencyProjectNames.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (versionName && dependentProjectName) {
|
||||
return formatMessage(analyticsChartMessages.dependentProjectVersionTooltip, {
|
||||
dependentProject: dependentProjectName,
|
||||
dependencyProject: dependencyProjectNames.join(', '),
|
||||
version: versionName,
|
||||
})
|
||||
}
|
||||
|
||||
return formatMessage(analyticsChartMessages.dependentOnProjectTooltip, {
|
||||
project: dependencyProjectNames.join(', '),
|
||||
})
|
||||
}
|
||||
|
||||
function getPaletteColorForIndex(index: number, palette: string[]): string {
|
||||
if (palette.length === 0) return ''
|
||||
|
||||
@@ -320,6 +379,10 @@ export function buildChartDatasets(
|
||||
activeStat: AnalyticsDashboardStat,
|
||||
palette: string[],
|
||||
selectedBreakdowns: readonly AnalyticsBreakdownPreset[],
|
||||
selectedFilters: AnalyticsSelectedFilters,
|
||||
dependentProjectTypesById: ReadonlyMap<string, readonly string[]>,
|
||||
projectNamesById: ReadonlyMap<string, string>,
|
||||
userNamesById: ReadonlyMap<string, string>,
|
||||
getVersionDisplayName: ((versionId: string) => string) | undefined,
|
||||
getVersionProjectName: ((versionId: string) => string | undefined) | undefined,
|
||||
formatMessage: FormatMessage,
|
||||
@@ -332,17 +395,43 @@ export function buildChartDatasets(
|
||||
|
||||
const dataLength = Math.max(sliceCount, timeSlices.length)
|
||||
const normalizedBreakdowns = selectedBreakdowns.filter((breakdown) => breakdown !== 'none')
|
||||
const projectNamesById = new Map(selectedProjects.map((project) => [project.id, project.name]))
|
||||
const normalizedFilters = normalizeAnalyticsSelectedFilters(selectedFilters)
|
||||
|
||||
function formatChartBreakdownLabels(breakdownValues: readonly string[]): string {
|
||||
const downloadReasonBreakdownIndex = normalizedBreakdowns.indexOf('download_reason')
|
||||
|
||||
return collapseRepeatedUnknownBreakdownLabels(
|
||||
normalizedBreakdowns.map((breakdown, index) => {
|
||||
const breakdownValue = breakdownValues[index] ?? ''
|
||||
if (breakdown === 'project') {
|
||||
if (breakdown === 'project' || breakdown === 'dependent_project_download') {
|
||||
if (
|
||||
breakdown === 'dependent_project_download' &&
|
||||
isNoDependentAnalyticsBreakdownValue(breakdownValue)
|
||||
) {
|
||||
return formatMessage(analyticsMessages.noDependent)
|
||||
}
|
||||
if (
|
||||
breakdown === 'dependent_project_download' &&
|
||||
isUnknownAnalyticsBreakdownValue(breakdownValue)
|
||||
) {
|
||||
return downloadReasonBreakdownIndex === -1
|
||||
? formatMessage(analyticsMessages.unknown)
|
||||
: formatAnalyticsDependentProjectFallbackLabel(
|
||||
breakdownValues[downloadReasonBreakdownIndex],
|
||||
formatMessage,
|
||||
)
|
||||
}
|
||||
|
||||
return projectNamesById.get(breakdownValue) ?? breakdownValue
|
||||
}
|
||||
|
||||
return formatBreakdownLabel(breakdownValue, breakdown, getVersionDisplayName, formatMessage)
|
||||
return formatBreakdownLabel(
|
||||
breakdownValue,
|
||||
breakdown,
|
||||
getVersionDisplayName,
|
||||
userNamesById,
|
||||
formatMessage,
|
||||
)
|
||||
}),
|
||||
formatMessage,
|
||||
).join(COMBINED_BREAKDOWN_LABEL_SEPARATOR)
|
||||
@@ -352,13 +441,27 @@ export function buildChartDatasets(
|
||||
normalizedBreakdowns.length > 0 &&
|
||||
!(normalizedBreakdowns.length === 1 && normalizedBreakdowns[0] === 'project')
|
||||
) {
|
||||
const hasVersionBreakdown = normalizedBreakdowns.includes('version_id')
|
||||
const hasDependentProjectBreakdown = normalizedBreakdowns.includes('dependent_project_download')
|
||||
const shouldShowDependentProjectTooltip =
|
||||
hasDependentProjectBreakdown && (selectedProjects.length > 1 || hasVersionBreakdown)
|
||||
const dataByBreakdown = new Map<string, number[]>()
|
||||
const breakdownValuesByKey = new Map<string, string[]>()
|
||||
const downloadTotalsByBreakdown = new Map<string, number>()
|
||||
const dependentOnProjectIdsByBreakdown = new Map<string, Set<string>>()
|
||||
|
||||
timeSlices.forEach((slice, sliceIndex) => {
|
||||
for (const point of slice) {
|
||||
if (!isProjectAnalyticsPointInSelectedProjects(point, selectedProjectIds)) continue
|
||||
if (
|
||||
!doesAnalyticsPointMatchNormalizedFilters(
|
||||
point,
|
||||
normalizedFilters,
|
||||
dependentProjectTypesById,
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const breakdownValues = getAnalyticsBreakdownValues(
|
||||
point,
|
||||
@@ -374,6 +477,11 @@ export function buildChartDatasets(
|
||||
dataByBreakdown.set(breakdownKey, new Array(dataLength).fill(0))
|
||||
breakdownValuesByKey.set(breakdownKey, breakdownValues)
|
||||
}
|
||||
if (shouldShowDependentProjectTooltip && point.metric_kind === 'downloads') {
|
||||
const projectIds = dependentOnProjectIdsByBreakdown.get(breakdownKey) ?? new Set<string>()
|
||||
projectIds.add(point.source_project)
|
||||
dependentOnProjectIdsByBreakdown.set(breakdownKey, projectIds)
|
||||
}
|
||||
|
||||
if (point.metric_kind === 'downloads') {
|
||||
downloadTotalsByBreakdown.set(
|
||||
@@ -402,6 +510,42 @@ export function buildChartDatasets(
|
||||
return Array.from(dataByBreakdown.entries()).map(([breakdownKey, data]) => {
|
||||
const breakdownValues = breakdownValuesByKey.get(breakdownKey) ?? []
|
||||
const fallbackColor = colorsByBreakdown.get(breakdownKey) ?? ''
|
||||
const versionBreakdownIndex = normalizedBreakdowns.indexOf('version_id')
|
||||
const dependentProjectBreakdownIndex = normalizedBreakdowns.indexOf(
|
||||
'dependent_project_download',
|
||||
)
|
||||
const versionName =
|
||||
hasVersionBreakdown && versionBreakdownIndex !== -1
|
||||
? getVersionDisplayName?.(breakdownValues[versionBreakdownIndex] ?? '')
|
||||
: undefined
|
||||
const dependentProjectId =
|
||||
dependentProjectBreakdownIndex !== -1
|
||||
? breakdownValues[dependentProjectBreakdownIndex]
|
||||
: undefined
|
||||
const dependentProjectName = dependentProjectId
|
||||
? isMissingDependentProjectValue(dependentProjectId)
|
||||
? undefined
|
||||
: (projectNamesById.get(dependentProjectId) ?? dependentProjectId)
|
||||
: undefined
|
||||
const versionProjectName =
|
||||
normalizedBreakdowns.length === 1 && normalizedBreakdowns[0] === 'version_id'
|
||||
? getVersionProjectName?.(breakdownValues[0] ?? '')
|
||||
: undefined
|
||||
const dependencyProjectNames = [...(dependentOnProjectIdsByBreakdown.get(breakdownKey) ?? [])]
|
||||
.map((projectId) => projectNamesById.get(projectId) ?? projectId)
|
||||
.sort((left, right) => left.localeCompare(right))
|
||||
const dependentProjectTooltip = dependentProjectId
|
||||
? isNoDependentAnalyticsBreakdownValue(dependentProjectId)
|
||||
? formatMessage(analyticsMessages.noDependentTooltip)
|
||||
: isUnknownAnalyticsBreakdownValue(dependentProjectId)
|
||||
? formatMessage(analyticsMessages.unknown)
|
||||
: formatDependentProjectDatasetTooltip(
|
||||
versionName,
|
||||
dependentProjectName,
|
||||
dependencyProjectNames,
|
||||
formatMessage,
|
||||
)
|
||||
: undefined
|
||||
const color =
|
||||
normalizedBreakdowns.length === 1
|
||||
? getBreakdownColor(
|
||||
@@ -414,10 +558,8 @@ export function buildChartDatasets(
|
||||
return {
|
||||
projectId: getAnalyticsBreakdownDatasetId(breakdownValues, normalizedBreakdowns),
|
||||
label: formatChartBreakdownLabels(breakdownValues),
|
||||
projectName:
|
||||
normalizedBreakdowns.length === 1 && normalizedBreakdowns[0] === 'version_id'
|
||||
? getVersionProjectName?.(breakdownValues[0] ?? '')
|
||||
: undefined,
|
||||
projectName: versionProjectName,
|
||||
tooltip: dependentProjectTooltip ?? formatDatasetTooltip(versionProjectName),
|
||||
data,
|
||||
borderColor: color,
|
||||
backgroundColor: color,
|
||||
@@ -432,6 +574,15 @@ export function buildChartDatasets(
|
||||
timeSlices.forEach((slice, sliceIndex) => {
|
||||
for (const point of slice) {
|
||||
if (!isProjectAnalyticsPointInSelectedProjects(point, selectedProjectIds)) continue
|
||||
if (
|
||||
!doesAnalyticsPointMatchNormalizedFilters(
|
||||
point,
|
||||
normalizedFilters,
|
||||
dependentProjectTypesById,
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (point.metric_kind === 'downloads') {
|
||||
downloadTotal += getMetricValue(point, 'downloads')
|
||||
@@ -481,6 +632,15 @@ export function buildChartDatasets(
|
||||
timeSlices.forEach((slice, sliceIndex) => {
|
||||
for (const point of slice) {
|
||||
if (!isProjectAnalyticsPointInSelectedProjects(point, selectedProjectIds)) continue
|
||||
if (
|
||||
!doesAnalyticsPointMatchNormalizedFilters(
|
||||
point,
|
||||
normalizedFilters,
|
||||
dependentProjectTypesById,
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const breakdownValues = getAnalyticsBreakdownValues(
|
||||
point,
|
||||
@@ -526,6 +686,10 @@ export function buildChartDatasets(
|
||||
return Array.from(dataByProjectBreakdown.entries()).map(([breakdownKey, data]) => {
|
||||
const breakdownValues = breakdownValuesByKey.get(breakdownKey) ?? []
|
||||
const fallbackColor = colorsByBreakdown.get(breakdownKey) ?? ''
|
||||
const versionProjectName =
|
||||
normalizedBreakdowns.length === 1 && normalizedBreakdowns[0] === 'version_id'
|
||||
? getVersionProjectName?.(breakdownValues[0] ?? '')
|
||||
: undefined
|
||||
const color =
|
||||
normalizedBreakdowns.length === 1
|
||||
? getBreakdownColor(
|
||||
@@ -538,10 +702,8 @@ export function buildChartDatasets(
|
||||
return {
|
||||
projectId: getAnalyticsBreakdownDatasetId(breakdownValues, normalizedBreakdowns),
|
||||
label: formatChartBreakdownLabels(breakdownValues),
|
||||
projectName:
|
||||
normalizedBreakdowns.length === 1 && normalizedBreakdowns[0] === 'version_id'
|
||||
? getVersionProjectName?.(breakdownValues[0] ?? '')
|
||||
: undefined,
|
||||
projectName: versionProjectName,
|
||||
tooltip: formatDatasetTooltip(versionProjectName),
|
||||
data,
|
||||
borderColor: color,
|
||||
backgroundColor: color,
|
||||
@@ -549,6 +711,10 @@ export function buildChartDatasets(
|
||||
})
|
||||
}
|
||||
|
||||
function isMissingDependentProjectValue(value: string | undefined): boolean {
|
||||
return isUnknownAnalyticsBreakdownValue(value) || isNoDependentAnalyticsBreakdownValue(value)
|
||||
}
|
||||
|
||||
export function getSliceCount(
|
||||
timeRange: Labrinth.Analytics.v3.TimeRange,
|
||||
fallback: number,
|
||||
|
||||
+27
-4
@@ -1,12 +1,15 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { useVIntl } from '@modrinth/ui'
|
||||
import { computed, type ComputedRef, ref, watch } from 'vue'
|
||||
|
||||
import { useTheme } from '~/composables/nuxt-accessors'
|
||||
import { isDarkTheme } from '~/plugins/theme/index.ts'
|
||||
import type {
|
||||
AnalyticsBreakdownPreset,
|
||||
AnalyticsDashboardContextValue,
|
||||
AnalyticsDashboardProject,
|
||||
AnalyticsDashboardStat,
|
||||
AnalyticsSelectedFilters,
|
||||
} from '~/providers/analytics/analytics'
|
||||
|
||||
import {
|
||||
@@ -47,12 +50,16 @@ export function useAnalyticsChartDatasets(
|
||||
| 'displayedPreviousTimeSlices'
|
||||
| 'displayedSelectedGroupBy'
|
||||
| 'displayedSelectedBreakdowns'
|
||||
| 'displayedSelectedFilters'
|
||||
| 'hiddenGraphDatasetIds'
|
||||
| 'hasExplicitGraphDatasetSelection'
|
||||
| 'isGraphDatasetSelectionActive'
|
||||
| 'selectedGraphDatasetIds'
|
||||
| 'defaultGraphDatasetIds'
|
||||
| 'topGraphDatasetIds'
|
||||
| 'projectNamesById'
|
||||
| 'userNamesById'
|
||||
| 'dependentProjectTypesById'
|
||||
| 'getVersionDisplayName'
|
||||
| 'getVersionProjectName'
|
||||
>,
|
||||
@@ -160,6 +167,10 @@ export function useAnalyticsChartDatasets(
|
||||
selectedProjects.value,
|
||||
legendPalette.value,
|
||||
context.displayedSelectedBreakdowns.value,
|
||||
context.displayedSelectedFilters.value,
|
||||
context.dependentProjectTypesById.value,
|
||||
context.projectNamesById.value,
|
||||
context.userNamesById.value,
|
||||
context.getVersionDisplayName,
|
||||
showProjectVersionNames.value ? context.getVersionProjectName : undefined,
|
||||
formatMessage,
|
||||
@@ -172,6 +183,10 @@ export function useAnalyticsChartDatasets(
|
||||
selectedProjects.value,
|
||||
legendPalette.value,
|
||||
context.displayedSelectedBreakdowns.value,
|
||||
context.displayedSelectedFilters.value,
|
||||
context.dependentProjectTypesById.value,
|
||||
context.projectNamesById.value,
|
||||
context.userNamesById.value,
|
||||
context.getVersionDisplayName,
|
||||
showProjectVersionNames.value ? context.getVersionProjectName : undefined,
|
||||
formatMessage,
|
||||
@@ -357,12 +372,16 @@ export function useAnalyticsChartDatasets(
|
||||
}
|
||||
|
||||
function buildDatasetsByStat(
|
||||
timeSlices: Parameters<typeof buildChartDatasets>[0],
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
|
||||
selectedProjects: AnalyticsDashboardProject[],
|
||||
palette: string[],
|
||||
selectedBreakdowns: Parameters<typeof buildChartDatasets>[4],
|
||||
getVersionDisplayName: Parameters<typeof buildChartDatasets>[5],
|
||||
getVersionProjectName: Parameters<typeof buildChartDatasets>[6],
|
||||
selectedBreakdowns: readonly AnalyticsBreakdownPreset[],
|
||||
selectedFilters: AnalyticsSelectedFilters,
|
||||
dependentProjectTypesById: ReadonlyMap<string, readonly string[]>,
|
||||
projectNamesById: ReadonlyMap<string, string>,
|
||||
userNamesById: ReadonlyMap<string, string>,
|
||||
getVersionDisplayName: (versionId: string) => string,
|
||||
getVersionProjectName: ((versionId: string) => string | undefined) | undefined,
|
||||
formatMessage: FormatMessage,
|
||||
sliceCount: number,
|
||||
) {
|
||||
@@ -374,6 +393,10 @@ function buildDatasetsByStat(
|
||||
stat,
|
||||
palette,
|
||||
selectedBreakdowns,
|
||||
selectedFilters,
|
||||
dependentProjectTypesById,
|
||||
projectNamesById,
|
||||
userNamesById,
|
||||
getVersionDisplayName,
|
||||
getVersionProjectName,
|
||||
formatMessage,
|
||||
|
||||
@@ -13,6 +13,7 @@ export type AnalyticsBreakdownItemType =
|
||||
| 'downloadSource'
|
||||
| 'gameVersion'
|
||||
| 'loader'
|
||||
| 'member'
|
||||
| 'monetization'
|
||||
| 'project'
|
||||
| 'projectVersion'
|
||||
@@ -83,6 +84,14 @@ export const analyticsMessages = defineMessages({
|
||||
id: 'analytics.value.unknown',
|
||||
defaultMessage: 'Unknown',
|
||||
},
|
||||
noDependent: {
|
||||
id: 'analytics.value.no-dependent',
|
||||
defaultMessage: 'None',
|
||||
},
|
||||
noDependentTooltip: {
|
||||
id: 'analytics.value.no-dependent-tooltip',
|
||||
defaultMessage: 'Downloaded for reasons other than being a dependency',
|
||||
},
|
||||
other: {
|
||||
id: 'analytics.value.other',
|
||||
defaultMessage: 'Other',
|
||||
@@ -175,6 +184,14 @@ export const analyticsMessages = defineMessages({
|
||||
id: 'analytics.filter.search.project-versions',
|
||||
defaultMessage: 'Search project versions...',
|
||||
},
|
||||
searchDependentProjectsPlaceholder: {
|
||||
id: 'analytics.filter.search.dependent-projects',
|
||||
defaultMessage: 'Search projects...',
|
||||
},
|
||||
searchMembersPlaceholder: {
|
||||
id: 'analytics.filter.search.members',
|
||||
defaultMessage: 'Search members...',
|
||||
},
|
||||
searchVersionsPlaceholder: {
|
||||
id: 'analytics.filter.search.versions',
|
||||
defaultMessage: 'Search versions...',
|
||||
@@ -351,6 +368,22 @@ export const analyticsBreakdownMessages = defineMessages({
|
||||
id: 'analytics.breakdown.download-reason',
|
||||
defaultMessage: 'Download reason',
|
||||
},
|
||||
members: {
|
||||
id: 'analytics.breakdown.members',
|
||||
defaultMessage: 'Member',
|
||||
},
|
||||
dependentProjectDownload: {
|
||||
id: 'analytics.breakdown.dependent-project-download',
|
||||
defaultMessage: 'Dependent project',
|
||||
},
|
||||
dependentProjectType: {
|
||||
id: 'analytics.breakdown.dependent-project-type',
|
||||
defaultMessage: 'Dependent project type',
|
||||
},
|
||||
dependentOn: {
|
||||
id: 'analytics.breakdown.dependent-on',
|
||||
defaultMessage: 'Dependent on',
|
||||
},
|
||||
versionId: {
|
||||
id: 'analytics.breakdown.project-version',
|
||||
defaultMessage: 'Project version',
|
||||
@@ -517,25 +550,33 @@ export const analyticsChartMessages = defineMessages({
|
||||
id: 'analytics.chart.action.show-top-eight',
|
||||
defaultMessage: 'Show top 8',
|
||||
},
|
||||
dependentOnProjectTooltip: {
|
||||
id: 'analytics.chart.tooltip.dependent-on-project',
|
||||
defaultMessage: 'Dependent on {project}',
|
||||
},
|
||||
dependentProjectVersionTooltip: {
|
||||
id: 'analytics.chart.tooltip.dependent-project-version',
|
||||
defaultMessage: '{dependentProject} dependent on {dependencyProject}, {version}',
|
||||
},
|
||||
tableSelectionLimited: {
|
||||
id: 'analytics.chart.table-selection.limited',
|
||||
defaultMessage:
|
||||
'Showing {limit} {itemType, select, project {{limit, plural, one {project} other {projects}}} country {{limit, plural, one {country} other {countries}}} monetization {{limit, plural, one {monetization value} other {monetization values}}} downloadSource {{limit, plural, one {download source} other {download sources}}} downloadReason {{limit, plural, one {download reason} other {download reasons}}} projectVersion {{limit, plural, one {project version} other {project versions}}} loader {{limit, plural, one {loader} other {loaders}}} gameVersion {{limit, plural, one {game version} other {game versions}}} other {{limit, plural, one {item} other {items}}}} from table',
|
||||
'Showing {limit} {itemType, select, project {{limit, plural, one {project} other {projects}}} country {{limit, plural, one {country} other {countries}}} monetization {{limit, plural, one {monetization value} other {monetization values}}} downloadSource {{limit, plural, one {download source} other {download sources}}} downloadReason {{limit, plural, one {download reason} other {download reasons}}} member {{limit, plural, one {member} other {members}}} projectVersion {{limit, plural, one {project version} other {project versions}}} loader {{limit, plural, one {loader} other {loaders}}} gameVersion {{limit, plural, one {game version} other {game versions}}} other {{limit, plural, one {item} other {items}}}} from table',
|
||||
},
|
||||
tableSelectionAll: {
|
||||
id: 'analytics.chart.table-selection.all',
|
||||
defaultMessage:
|
||||
'Showing all {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
|
||||
'Showing all {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} member {{count, plural, one {member} other {members}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
|
||||
},
|
||||
tableSelectionTop: {
|
||||
id: 'analytics.chart.table-selection.top',
|
||||
defaultMessage:
|
||||
'Showing top {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
|
||||
'Showing top {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} member {{count, plural, one {member} other {members}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
|
||||
},
|
||||
tableSelectionCount: {
|
||||
id: 'analytics.chart.table-selection.count',
|
||||
defaultMessage:
|
||||
'Showing {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
|
||||
'Showing {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} member {{count, plural, one {member} other {members}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
|
||||
},
|
||||
lineView: {
|
||||
id: 'analytics.chart.view.line',
|
||||
@@ -781,6 +822,10 @@ export function formatAnalyticsBreakdownLabel(
|
||||
return formatMessage(analyticsBreakdownMessages.userAgent)
|
||||
case 'download_reason':
|
||||
return formatMessage(analyticsBreakdownMessages.downloadReason)
|
||||
case 'user_id':
|
||||
return formatMessage(analyticsBreakdownMessages.members)
|
||||
case 'dependent_project_download':
|
||||
return formatMessage(analyticsBreakdownMessages.dependentProjectDownload)
|
||||
case 'version_id':
|
||||
return formatMessage(analyticsBreakdownMessages.versionId)
|
||||
case 'loader':
|
||||
@@ -810,6 +855,10 @@ export function getAnalyticsBreakdownItemType(
|
||||
return 'downloadSource'
|
||||
case 'download_reason':
|
||||
return 'downloadReason'
|
||||
case 'user_id':
|
||||
return 'member'
|
||||
case 'dependent_project_download':
|
||||
return 'project'
|
||||
case 'version_id':
|
||||
return 'projectVersion'
|
||||
case 'loader':
|
||||
@@ -853,6 +902,18 @@ export function formatAnalyticsDownloadReasonLabel(
|
||||
}
|
||||
}
|
||||
|
||||
export function formatAnalyticsDependentProjectFallbackLabel(
|
||||
downloadReason: string | undefined,
|
||||
formatMessage: FormatMessage,
|
||||
): string {
|
||||
const normalizedReason = downloadReason?.trim().toLowerCase()
|
||||
if (normalizedReason === 'standalone' || normalizedReason === 'update') {
|
||||
return formatMessage(analyticsMessages.noDependent)
|
||||
}
|
||||
|
||||
return formatMessage(analyticsMessages.unknown)
|
||||
}
|
||||
|
||||
export function formatAnalyticsDownloadSourceLabel(
|
||||
source: string,
|
||||
formatMessage: FormatMessage,
|
||||
|
||||
@@ -19,6 +19,8 @@ import type {
|
||||
MutableRouteQuery,
|
||||
} from '~/providers/analytics/analytics-types'
|
||||
|
||||
import { getAnalyticsBreakdownsWithSharedStats } from './query-builder/query-filter-utils'
|
||||
|
||||
export const DEFAULT_TIMEFRAME_PRESET: AnalyticsTimeframePreset = 'last_30_days'
|
||||
export const DEFAULT_TIMEFRAME_MODE: AnalyticsTimeframeMode = 'preset'
|
||||
export const DEFAULT_LAST_TIMEFRAME_AMOUNT = 1
|
||||
@@ -73,9 +75,11 @@ const BREAKDOWN_PRESET_VALUES: AnalyticsBreakdownPreset[] = [
|
||||
'monetization',
|
||||
'user_agent',
|
||||
'download_reason',
|
||||
'user_id',
|
||||
'version_id',
|
||||
'loader',
|
||||
'game_version',
|
||||
'dependent_project_download',
|
||||
]
|
||||
|
||||
const ANALYTICS_DASHBOARD_STAT_VALUES: AnalyticsDashboardStat[] = [
|
||||
@@ -89,15 +93,18 @@ const ANALYTICS_GRAPH_VIEW_MODE_VALUES: AnalyticsGraphViewMode[] = ['line', 'are
|
||||
const ANALYTICS_TABLE_SORT_COLUMN_VALUES: AnalyticsTableSortColumn[] = [
|
||||
'date',
|
||||
'project',
|
||||
'dependent_on',
|
||||
'breakdown',
|
||||
'breakdown_project',
|
||||
'breakdown_country',
|
||||
'breakdown_monetization',
|
||||
'breakdown_user_agent',
|
||||
'breakdown_download_reason',
|
||||
'breakdown_user_id',
|
||||
'breakdown_version_id',
|
||||
'breakdown_loader',
|
||||
'breakdown_game_version',
|
||||
'breakdown_dependent_project_download',
|
||||
'views',
|
||||
'downloads',
|
||||
'revenue',
|
||||
@@ -131,9 +138,12 @@ const QUERY_KEY_FILTER_MONETIZATION = 'a_monetization'
|
||||
const QUERY_KEY_FILTER_USER_AGENT = 'a_user_agent'
|
||||
const QUERY_KEY_FILTER_LEGACY_DOWNLOAD_SOURCE = 'a_download_source'
|
||||
const QUERY_KEY_FILTER_DOWNLOAD_REASON = 'a_download_reason'
|
||||
const QUERY_KEY_FILTER_USER_ID = 'a_user_id'
|
||||
const QUERY_KEY_FILTER_VERSION_ID = 'a_version_id'
|
||||
const QUERY_KEY_FILTER_GAME_VERSION = 'a_game_version'
|
||||
const QUERY_KEY_FILTER_LOADER_TYPE = 'a_loader_type'
|
||||
const QUERY_KEY_FILTER_DEPENDENT_PROJECT_ID = 'a_dependent_project_id'
|
||||
const QUERY_KEY_FILTER_DEPENDENT_PROJECT_TYPE = 'a_dependent_project_type'
|
||||
const QUERY_KEY_STAT = 'a_stat'
|
||||
const QUERY_KEY_GRAPH_VIEW_MODE = 'a_chart'
|
||||
const QUERY_KEY_GRAPH_RATIO_MODE = 'a_ratio'
|
||||
@@ -154,9 +164,12 @@ const URL_FILTER_CATEGORIES: Exclude<AnalyticsQueryFilterCategory, 'project'>[]
|
||||
'monetization',
|
||||
'user_agent',
|
||||
'download_reason',
|
||||
'user_id',
|
||||
'version_id',
|
||||
'game_version',
|
||||
'loader_type',
|
||||
'dependent_project_id',
|
||||
'dependent_project_type',
|
||||
]
|
||||
|
||||
const FILTER_QUERY_KEY_BY_CATEGORY: Record<
|
||||
@@ -168,9 +181,12 @@ const FILTER_QUERY_KEY_BY_CATEGORY: Record<
|
||||
monetization: QUERY_KEY_FILTER_MONETIZATION,
|
||||
user_agent: QUERY_KEY_FILTER_USER_AGENT,
|
||||
download_reason: QUERY_KEY_FILTER_DOWNLOAD_REASON,
|
||||
user_id: QUERY_KEY_FILTER_USER_ID,
|
||||
version_id: QUERY_KEY_FILTER_VERSION_ID,
|
||||
game_version: QUERY_KEY_FILTER_GAME_VERSION,
|
||||
loader_type: QUERY_KEY_FILTER_LOADER_TYPE,
|
||||
dependent_project_id: QUERY_KEY_FILTER_DEPENDENT_PROJECT_ID,
|
||||
dependent_project_type: QUERY_KEY_FILTER_DEPENDENT_PROJECT_TYPE,
|
||||
}
|
||||
|
||||
const ANALYTICS_QUERY_KEYS = [
|
||||
@@ -189,9 +205,12 @@ const ANALYTICS_QUERY_KEYS = [
|
||||
QUERY_KEY_FILTER_USER_AGENT,
|
||||
QUERY_KEY_FILTER_LEGACY_DOWNLOAD_SOURCE,
|
||||
QUERY_KEY_FILTER_DOWNLOAD_REASON,
|
||||
QUERY_KEY_FILTER_USER_ID,
|
||||
QUERY_KEY_FILTER_VERSION_ID,
|
||||
QUERY_KEY_FILTER_GAME_VERSION,
|
||||
QUERY_KEY_FILTER_LOADER_TYPE,
|
||||
QUERY_KEY_FILTER_DEPENDENT_PROJECT_ID,
|
||||
QUERY_KEY_FILTER_DEPENDENT_PROJECT_TYPE,
|
||||
QUERY_KEY_STAT,
|
||||
QUERY_KEY_GRAPH_VIEW_MODE,
|
||||
QUERY_KEY_GRAPH_RATIO_MODE,
|
||||
@@ -212,9 +231,12 @@ export function buildEmptySelectedFilters(): AnalyticsSelectedFilters {
|
||||
monetization: [],
|
||||
user_agent: [],
|
||||
download_reason: [],
|
||||
user_id: [],
|
||||
version_id: [],
|
||||
game_version: [],
|
||||
loader_type: [],
|
||||
dependent_project_id: [],
|
||||
dependent_project_type: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +277,7 @@ function normalizeFilterQueryValues(
|
||||
.filter((value) => PROJECT_STATUS_FILTER_VALUES.includes(value))
|
||||
}
|
||||
|
||||
if (category !== 'loader_type') {
|
||||
if (category !== 'loader_type' && category !== 'dependent_project_type') {
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -456,7 +478,7 @@ export function getAnalyticsBreakdownPresetsForProjectSelection(
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedBreakdowns
|
||||
return getAnalyticsBreakdownsWithSharedStats(normalizedBreakdowns)
|
||||
}
|
||||
|
||||
export function getAnalyticsBreakdownPresetForProjectSelection(
|
||||
@@ -776,6 +798,12 @@ export function hasAnalyticsProjectSelectionQuery(query: LocationQuery): boolean
|
||||
return parseListQueryValue(query[QUERY_KEY_PROJECT_IDS]).length > 0
|
||||
}
|
||||
|
||||
export function hasAnalyticsAllProjectSelectionQuery(query: LocationQuery): boolean {
|
||||
return parseListQueryValue(query[QUERY_KEY_PROJECT_IDS]).includes(
|
||||
PROJECT_SELECTION_ALL_QUERY_VALUE,
|
||||
)
|
||||
}
|
||||
|
||||
export function hasAnalyticsGraphProjectEventsVisibilityQuery(query: LocationQuery): boolean {
|
||||
return query[QUERY_KEY_GRAPH_PROJECT_EVENTS_VISIBILITY] !== undefined
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="mr-2.5 flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
v-if="!hideIcon"
|
||||
v-tooltip="iconTooltip"
|
||||
class="flex size-6 shrink-0 items-center justify-center overflow-hidden rounded text-primary"
|
||||
>
|
||||
<img
|
||||
v-if="iconUrl"
|
||||
:src="iconUrl"
|
||||
:alt="formatMessage(analyticsMessages.projectIconAlt, { name: label })"
|
||||
class="h-6 w-6 rounded object-cover"
|
||||
/>
|
||||
<BoxIcon v-else class="h-full w-full" />
|
||||
</span>
|
||||
<component
|
||||
:is="labelHref ? 'a' : 'span'"
|
||||
v-tooltip="labelTooltip"
|
||||
:href="labelHref"
|
||||
:target="labelHref ? '_blank' : undefined"
|
||||
:rel="labelHref ? 'noopener noreferrer' : undefined"
|
||||
class="line-clamp-2 min-w-0 truncate text-wrap font-semibold leading-tight text-primary"
|
||||
:class="{ 'hover:underline': labelHref }"
|
||||
:title="label"
|
||||
>
|
||||
{{ label }}
|
||||
</component>
|
||||
<component
|
||||
:is="organizationHref ? 'a' : 'span'"
|
||||
v-if="organizationTooltip"
|
||||
v-tooltip="organizationTooltip"
|
||||
:href="organizationHref"
|
||||
:target="organizationHref ? '_blank' : undefined"
|
||||
:rel="organizationHref ? 'noopener noreferrer' : undefined"
|
||||
:aria-label="organizationTooltip"
|
||||
class="flex size-4 shrink-0 items-center text-primary"
|
||||
:class="{ 'hover:underline': organizationHref }"
|
||||
>
|
||||
<OrganizationIcon class="size-4" />
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BoxIcon, OrganizationIcon } from '@modrinth/assets'
|
||||
import { useVIntl } from '@modrinth/ui'
|
||||
|
||||
import { analyticsMessages } from '../analytics-messages.ts'
|
||||
|
||||
defineProps<{
|
||||
label: string
|
||||
iconUrl?: string
|
||||
iconTooltip?: string
|
||||
hideIcon?: boolean
|
||||
labelHref?: string
|
||||
labelTooltip?: string
|
||||
organizationHref?: string
|
||||
organizationTooltip?: string
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
</script>
|
||||
+5
-12
@@ -23,7 +23,6 @@ type BuildAnalyticsTableColumnsOptions = {
|
||||
selectedBreakdowns: readonly AnalyticsTableBreakdownPreset[]
|
||||
selectedFilters: AnalyticsSelectedFilters
|
||||
showBreakdownColumn: boolean
|
||||
showProjectVersionProjectColumn: boolean
|
||||
formatMessage: FormatMessage
|
||||
getRelevantAnalyticsDashboardStats: (
|
||||
breakdowns: readonly AnalyticsBreakdownPreset[],
|
||||
@@ -43,7 +42,6 @@ export function buildAnalyticsTableColumns({
|
||||
selectedBreakdowns,
|
||||
selectedFilters,
|
||||
showBreakdownColumn,
|
||||
showProjectVersionProjectColumn,
|
||||
formatMessage,
|
||||
getRelevantAnalyticsDashboardStats,
|
||||
}: BuildAnalyticsTableColumnsOptions): TableColumn<AnalyticsTableColumnKey>[] {
|
||||
@@ -66,20 +64,11 @@ export function buildAnalyticsTableColumns({
|
||||
key: getAnalyticsTableBreakdownColumnKey(breakdown),
|
||||
label: getAnalyticsTableBreakdownColumnLabel(breakdown, formatMessage),
|
||||
enableSorting: true,
|
||||
width: breakdown === 'project' ? '25%' : undefined,
|
||||
width: breakdown === 'project' && selectedBreakdowns.length === 1 ? '45%' : undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (showProjectVersionProjectColumn) {
|
||||
nextColumns.push({
|
||||
key: 'project',
|
||||
label: formatAnalyticsBreakdownLabel('project', formatMessage),
|
||||
enableSorting: true,
|
||||
width: '25%',
|
||||
})
|
||||
}
|
||||
|
||||
for (const stat of stats) {
|
||||
const column = getAnalyticsTableMetricColumn(stat, formatMessage)
|
||||
if (column) {
|
||||
@@ -102,6 +91,7 @@ export function getAnalyticsTableMetricColumn(
|
||||
enableSorting: true,
|
||||
defaultSortDirection: 'desc',
|
||||
align: 'right',
|
||||
width: '20%',
|
||||
}
|
||||
case 'downloads':
|
||||
return {
|
||||
@@ -110,6 +100,7 @@ export function getAnalyticsTableMetricColumn(
|
||||
enableSorting: true,
|
||||
defaultSortDirection: 'desc',
|
||||
align: 'right',
|
||||
width: '20%',
|
||||
}
|
||||
case 'revenue':
|
||||
return {
|
||||
@@ -118,6 +109,7 @@ export function getAnalyticsTableMetricColumn(
|
||||
enableSorting: true,
|
||||
defaultSortDirection: 'desc',
|
||||
align: 'right',
|
||||
width: '20%',
|
||||
}
|
||||
case 'playtime':
|
||||
return {
|
||||
@@ -126,6 +118,7 @@ export function getAnalyticsTableMetricColumn(
|
||||
enableSorting: true,
|
||||
defaultSortDirection: 'desc',
|
||||
align: 'right',
|
||||
width: '20%',
|
||||
}
|
||||
default:
|
||||
return null
|
||||
|
||||
+2
@@ -67,6 +67,8 @@ function getAnalyticsTableCsvCellValue(
|
||||
return row.date
|
||||
case 'project':
|
||||
return row.project
|
||||
case 'dependent_on':
|
||||
return row.dependent_on
|
||||
case 'breakdown':
|
||||
return row.breakdownDisplay
|
||||
case 'views':
|
||||
|
||||
+94
-7
@@ -1,8 +1,11 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
|
||||
import type {
|
||||
AnalyticsBreakdownPreset,
|
||||
AnalyticsDashboardStat,
|
||||
import {
|
||||
type AnalyticsBreakdownPreset,
|
||||
type AnalyticsDashboardStat,
|
||||
type AnalyticsSelectedFilters,
|
||||
doesAnalyticsPointMatchNormalizedFilters,
|
||||
normalizeAnalyticsSelectedFilters,
|
||||
} from '~/providers/analytics/analytics'
|
||||
|
||||
import {
|
||||
@@ -12,13 +15,18 @@ import {
|
||||
getSliceCount,
|
||||
} from '../analytics-chart/analytics-chart-utils'
|
||||
import type { FormatMessage } from '../analytics-messages'
|
||||
import { analyticsMessages } from '../analytics-messages'
|
||||
import {
|
||||
analyticsMessages,
|
||||
formatAnalyticsDependentProjectFallbackLabel,
|
||||
} from '../analytics-messages'
|
||||
import {
|
||||
ALL_BREAKDOWN_VALUE,
|
||||
COMBINED_BREAKDOWN_LABEL_SEPARATOR,
|
||||
getAnalyticsBreakdownDatasetId,
|
||||
getAnalyticsBreakdownKey,
|
||||
getAnalyticsBreakdownValues,
|
||||
isNoDependentAnalyticsBreakdownValue,
|
||||
isUnknownAnalyticsBreakdownValue,
|
||||
} from '../breakdown'
|
||||
import { getAnalyticsTableBreakdownColumnKey } from './analytics-table-columns'
|
||||
import type {
|
||||
@@ -36,8 +44,12 @@ type BuildAnalyticsTableRowsOptions = {
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[]
|
||||
selectedBreakdowns: readonly AnalyticsTableBreakdownPreset[]
|
||||
selectedProjectIds: ReadonlySet<string>
|
||||
selectedFilters: AnalyticsSelectedFilters
|
||||
dependentProjectTypesById: ReadonlyMap<string, readonly string[]>
|
||||
includeDependentProjectTooltipContext: boolean
|
||||
relevantStats: ReadonlySet<AnalyticsDashboardStat>
|
||||
projectNamesById: ReadonlyMap<string, string>
|
||||
userNamesById: ReadonlyMap<string, string>
|
||||
getVersionDisplayName: (versionId: string) => string
|
||||
getVersionProjectName: (versionId: string) => string | undefined
|
||||
showTimeInBucketLabel: boolean
|
||||
@@ -51,8 +63,12 @@ export function buildAnalyticsTableRows({
|
||||
timeSlices,
|
||||
selectedBreakdowns,
|
||||
selectedProjectIds,
|
||||
selectedFilters,
|
||||
dependentProjectTypesById,
|
||||
includeDependentProjectTooltipContext,
|
||||
relevantStats,
|
||||
projectNamesById,
|
||||
userNamesById,
|
||||
getVersionDisplayName,
|
||||
getVersionProjectName,
|
||||
showTimeInBucketLabel,
|
||||
@@ -72,6 +88,7 @@ export function buildAnalyticsTableRows({
|
||||
const projectDisplayValues = new Map<string, string>()
|
||||
const nextRows = new Map<string, AnalyticsTableRow>()
|
||||
const bucketLabelsBySliceIndex = new Map<number, { date: string; dateMs: number }>()
|
||||
const normalizedFilters = normalizeAnalyticsSelectedFilters(selectedFilters)
|
||||
|
||||
function getBreakdownDisplayValue(
|
||||
breakdownValue: string,
|
||||
@@ -84,6 +101,7 @@ export function buildAnalyticsTableRows({
|
||||
breakdownValue,
|
||||
breakdown,
|
||||
projectNamesById,
|
||||
userNamesById,
|
||||
getVersionDisplayName,
|
||||
formatMessage,
|
||||
)
|
||||
@@ -111,6 +129,15 @@ export function buildAnalyticsTableRows({
|
||||
return displayValue
|
||||
}
|
||||
|
||||
function getProjectVersionIdForBreakdownValues(breakdownValues: readonly string[]) {
|
||||
const versionBreakdownIndex = selectedBreakdowns.indexOf('version_id')
|
||||
if (versionBreakdownIndex === -1) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return breakdownValues[versionBreakdownIndex] ?? ''
|
||||
}
|
||||
|
||||
function getBreakdownDisplays(breakdownValues: readonly string[]) {
|
||||
const displays: AnalyticsTableBreakdownDisplayValues = {}
|
||||
|
||||
@@ -118,6 +145,19 @@ export function buildAnalyticsTableRows({
|
||||
displays[breakdown] = getBreakdownDisplayValue(breakdownValues[index] ?? '', breakdown)
|
||||
})
|
||||
|
||||
const dependentProjectBreakdownIndex = selectedBreakdowns.indexOf('dependent_project_download')
|
||||
const downloadReasonBreakdownIndex = selectedBreakdowns.indexOf('download_reason')
|
||||
if (
|
||||
dependentProjectBreakdownIndex !== -1 &&
|
||||
downloadReasonBreakdownIndex !== -1 &&
|
||||
isUnknownAnalyticsBreakdownValue(breakdownValues[dependentProjectBreakdownIndex])
|
||||
) {
|
||||
displays.dependent_project_download = formatAnalyticsDependentProjectFallbackLabel(
|
||||
breakdownValues[downloadReasonBreakdownIndex],
|
||||
formatMessage,
|
||||
)
|
||||
}
|
||||
|
||||
return displays
|
||||
}
|
||||
|
||||
@@ -157,6 +197,7 @@ export function buildAnalyticsTableRows({
|
||||
function createRow(
|
||||
rowId: string,
|
||||
breakdownValues: readonly string[],
|
||||
dependentOnProjectId?: string,
|
||||
bucketLabel?: { date: string; dateMs: number },
|
||||
) {
|
||||
const breakdownKey =
|
||||
@@ -169,6 +210,12 @@ export function buildAnalyticsTableRows({
|
||||
date: bucketLabel?.date ?? '',
|
||||
dateMs: bucketLabel?.dateMs ?? 0,
|
||||
project: getProjectDisplayValueForBreakdownValues(breakdownValues),
|
||||
projectVersionId: getProjectVersionIdForBreakdownValues(breakdownValues),
|
||||
dependent_on: dependentOnProjectId
|
||||
? (projectNamesById.get(dependentOnProjectId) ?? dependentOnProjectId)
|
||||
: '',
|
||||
dependentOnProjectId: dependentOnProjectId ?? '',
|
||||
dependentOnProjectIds: dependentOnProjectId ? [dependentOnProjectId] : [],
|
||||
breakdown: breakdownKey,
|
||||
breakdownValues: Object.fromEntries(
|
||||
selectedBreakdowns.map((breakdown, index) => [breakdown, breakdownValues[index] ?? '']),
|
||||
@@ -190,6 +237,18 @@ export function buildAnalyticsTableRows({
|
||||
return row
|
||||
}
|
||||
|
||||
function addDependentOnProjectIdToRow(row: AnalyticsTableRow, projectId: string | undefined) {
|
||||
if (!projectId || row.dependentOnProjectIds.includes(projectId)) {
|
||||
return
|
||||
}
|
||||
|
||||
row.dependentOnProjectIds.push(projectId)
|
||||
if (!row.dependentOnProjectId) {
|
||||
row.dependentOnProjectId = projectId
|
||||
row.dependent_on = projectNamesById.get(projectId) ?? projectId
|
||||
}
|
||||
}
|
||||
|
||||
if (!includeDate && selectedBreakdowns.length === 0) {
|
||||
createRow(ALL_PROJECTS_BREAKDOWN_VALUE, [])
|
||||
}
|
||||
@@ -211,6 +270,15 @@ export function buildAnalyticsTableRows({
|
||||
if (!selectedProjectIds.has(point.source_project)) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
!doesAnalyticsPointMatchNormalizedFilters(
|
||||
point,
|
||||
normalizedFilters,
|
||||
dependentProjectTypesById,
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const pointStat = getAnalyticsTableStatForMetric(point.metric_kind)
|
||||
if (!pointStat || !relevantStats.has(pointStat)) {
|
||||
@@ -226,12 +294,21 @@ export function buildAnalyticsTableRows({
|
||||
}
|
||||
|
||||
const nextBucketLabel = includeDate ? (bucketLabel ?? getBucketLabel(sliceIndex)) : undefined
|
||||
const dependentOnProjectId = includeDependentProjectTooltipContext
|
||||
? point.source_project
|
||||
: undefined
|
||||
const dependentTooltipProjectId = selectedBreakdowns.includes('dependent_project_download')
|
||||
? point.source_project
|
||||
: undefined
|
||||
const breakdownKey =
|
||||
breakdownValues.length === 0
|
||||
? ALL_PROJECTS_BREAKDOWN_VALUE
|
||||
: getAnalyticsBreakdownKey(breakdownValues)
|
||||
const rowId = includeDate ? `${nextBucketLabel?.dateMs ?? 0}::${breakdownKey}` : breakdownKey
|
||||
const row = nextRows.get(rowId) ?? createRow(rowId, breakdownValues, nextBucketLabel)
|
||||
const row =
|
||||
nextRows.get(rowId) ??
|
||||
createRow(rowId, breakdownValues, dependentOnProjectId, nextBucketLabel)
|
||||
addDependentOnProjectIdToRow(row, dependentTooltipProjectId)
|
||||
addAnalyticsMetricToTableRow(row, point)
|
||||
}
|
||||
})
|
||||
@@ -292,11 +369,21 @@ function formatAnalyticsTableBreakdownDisplayValue(
|
||||
value: string,
|
||||
breakdown: AnalyticsTableBreakdownPreset,
|
||||
projectNamesById: ReadonlyMap<string, string>,
|
||||
userNamesById: ReadonlyMap<string, string>,
|
||||
getVersionDisplayName: (versionId: string) => string,
|
||||
formatMessage: FormatMessage,
|
||||
): string {
|
||||
if (breakdown === 'project') {
|
||||
if (breakdown === 'project' || breakdown === 'dependent_project_download') {
|
||||
if (breakdown === 'dependent_project_download') {
|
||||
if (isNoDependentAnalyticsBreakdownValue(value)) {
|
||||
return formatMessage(analyticsMessages.noDependent)
|
||||
}
|
||||
if (isUnknownAnalyticsBreakdownValue(value)) {
|
||||
return formatMessage(analyticsMessages.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
return projectNamesById.get(value) ?? value
|
||||
}
|
||||
return formatBreakdownLabel(value, breakdown, getVersionDisplayName, formatMessage)
|
||||
return formatBreakdownLabel(value, breakdown, getVersionDisplayName, userNamesById, formatMessage)
|
||||
}
|
||||
|
||||
+3
-1
@@ -3,7 +3,7 @@ import type { TableColumn } from '@modrinth/ui'
|
||||
import { isAnalyticsTableBreakdownColumnKey } from './analytics-table-columns'
|
||||
import type { AnalyticsTableColumnKey, AnalyticsTableRow } from './analytics-table-types'
|
||||
|
||||
const SEARCHABLE_COLUMN_KEYS = new Set<AnalyticsTableColumnKey>(['date', 'project'])
|
||||
const SEARCHABLE_COLUMN_KEYS = new Set<AnalyticsTableColumnKey>(['date', 'project', 'dependent_on'])
|
||||
|
||||
export function getAnalyticsTableSearchableColumns(
|
||||
columns: TableColumn<AnalyticsTableColumnKey>[],
|
||||
@@ -39,6 +39,8 @@ function getAnalyticsTableSearchableCellValue(
|
||||
return row.date
|
||||
case 'project':
|
||||
return row.project
|
||||
case 'dependent_on':
|
||||
return row.dependent_on
|
||||
case 'breakdown':
|
||||
return row.breakdownDisplay
|
||||
default:
|
||||
|
||||
+9
@@ -129,6 +129,15 @@ function getAnalyticsTableRowComparator(
|
||||
directionFactor,
|
||||
sortCollator,
|
||||
)
|
||||
case 'dependent_on':
|
||||
return (left, right) =>
|
||||
compareAnalyticsTableRows(
|
||||
left,
|
||||
right,
|
||||
sortCollator.compare(left.dependent_on, right.dependent_on),
|
||||
directionFactor,
|
||||
sortCollator,
|
||||
)
|
||||
case 'breakdown':
|
||||
return (left, right) =>
|
||||
compareAnalyticsTableRows(
|
||||
|
||||
+5
-1
@@ -18,11 +18,15 @@ export type AnalyticsTableSortState = {
|
||||
export type AnalyticsTableSortDirectionValue = AnalyticsTableSortDirection
|
||||
|
||||
export type AnalyticsTableRow = {
|
||||
[key: string]: string | number | AnalyticsTableBreakdownDisplayValues
|
||||
[key: string]: string | number | string[] | AnalyticsTableBreakdownDisplayValues
|
||||
id: string
|
||||
date: string
|
||||
dateMs: number
|
||||
project: string
|
||||
projectVersionId: string
|
||||
dependent_on: string
|
||||
dependentOnProjectId: string
|
||||
dependentOnProjectIds: string[]
|
||||
breakdown: string
|
||||
breakdownValues: AnalyticsTableBreakdownDisplayValues
|
||||
breakdownDisplays: AnalyticsTableBreakdownDisplayValues
|
||||
|
||||
@@ -55,32 +55,92 @@
|
||||
<template #cell-date="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-breakdown_project="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<template #cell-breakdown_project="{ row, value }">
|
||||
<ProjectCell
|
||||
:label="getProjectCellLabel(value)"
|
||||
:icon-url="getProjectIconUrl(row.breakdownValues.project)"
|
||||
:icon-tooltip="getProjectCellLabel(value)"
|
||||
:label-href="getProjectPageHref(row.breakdownValues.project)"
|
||||
:organization-href="getProjectOrganizationPageHref(row.breakdownValues.project)"
|
||||
:organization-tooltip="getProjectOrganizationName(row.breakdownValues.project)"
|
||||
/>
|
||||
</template>
|
||||
<template #cell-breakdown_country="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<span class="mr-2.5 text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-breakdown_monetization="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<span class="mr-2.5 text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-breakdown_user_agent="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<span class="mr-2.5 text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-breakdown_download_reason="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<span class="mr-2.5 text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-breakdown_version_id="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<template #cell-breakdown_user_id="{ row, value }">
|
||||
<component
|
||||
:is="getUserPageHref(row.breakdownValues.user_id) ? 'a' : 'span'"
|
||||
:href="getUserPageHref(row.breakdownValues.user_id)"
|
||||
:target="getUserPageHref(row.breakdownValues.user_id) ? '_blank' : undefined"
|
||||
:rel="getUserPageHref(row.breakdownValues.user_id) ? 'noopener noreferrer' : undefined"
|
||||
class="mr-2.5 flex min-w-0 items-center gap-2 text-primary"
|
||||
:class="{ 'hover:underline': getUserPageHref(row.breakdownValues.user_id) }"
|
||||
>
|
||||
<span
|
||||
v-tooltip="getUserCellLabel(value)"
|
||||
class="flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-primary"
|
||||
>
|
||||
<img
|
||||
v-if="getUserAvatarUrl(row.breakdownValues.user_id)"
|
||||
:src="getUserAvatarUrl(row.breakdownValues.user_id)"
|
||||
:alt="getUserCellLabel(value)"
|
||||
class="h-6 w-6 rounded-full object-cover"
|
||||
/>
|
||||
<UserIcon v-else class="h-full w-full" />
|
||||
</span>
|
||||
<span class="min-w-0 truncate font-semibold leading-tight text-primary">
|
||||
{{ value }}
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
<template #cell-breakdown_dependent_project_download="{ row, value }">
|
||||
<ProjectCell
|
||||
:label="getProjectCellLabel(value)"
|
||||
:icon-url="
|
||||
isMissingDependentProjectValue(row.breakdownValues.dependent_project_download)
|
||||
? undefined
|
||||
: getProjectIconUrl(row.breakdownValues.dependent_project_download)
|
||||
"
|
||||
:icon-tooltip="getProjectCellLabel(value)"
|
||||
:hide-icon="
|
||||
isMissingDependentProjectValue(row.breakdownValues.dependent_project_download)
|
||||
"
|
||||
:label-href="
|
||||
isMissingDependentProjectValue(row.breakdownValues.dependent_project_download)
|
||||
? undefined
|
||||
: getProjectPageHref(row.breakdownValues.dependent_project_download)
|
||||
"
|
||||
:label-tooltip="getDependentProjectTooltip(row)"
|
||||
/>
|
||||
</template>
|
||||
<template #cell-breakdown_version_id="{ row, value }">
|
||||
<component
|
||||
:is="getVersionPageHref(row.projectVersionId) ? 'a' : 'span'"
|
||||
v-tooltip="getVersionProjectName(row.projectVersionId)"
|
||||
:href="getVersionPageHref(row.projectVersionId)"
|
||||
:target="getVersionPageHref(row.projectVersionId) ? '_blank' : undefined"
|
||||
:rel="getVersionPageHref(row.projectVersionId) ? 'noopener noreferrer' : undefined"
|
||||
class="mr-2.5 text-primary"
|
||||
:class="{ 'hover:underline': getVersionPageHref(row.projectVersionId) }"
|
||||
>
|
||||
{{ value }}
|
||||
</component>
|
||||
</template>
|
||||
<template #cell-breakdown_loader="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<span class="mr-2.5 text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-breakdown_game_version="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-project="{ value }">
|
||||
<span class="text-primary">{{ value }}</span>
|
||||
<span class="mr-2.5 text-primary">{{ value }}</span>
|
||||
</template>
|
||||
<template #cell-views="{ row }">
|
||||
<span>{{ formatInteger(row.views) }}</span>
|
||||
@@ -130,7 +190,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, DropdownIcon, SearchIcon } from '@modrinth/assets'
|
||||
import { DownloadIcon, DropdownIcon, SearchIcon, UserIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
OverflowMenu,
|
||||
@@ -159,10 +219,15 @@ import {
|
||||
} from '../analytics-chart/analytics-chart-utils.ts'
|
||||
import {
|
||||
analyticsBreakdownMessages,
|
||||
analyticsChartMessages,
|
||||
analyticsMessages,
|
||||
analyticsTableMessages,
|
||||
} from '../analytics-messages.ts'
|
||||
import AnalyticsLoadingBar from '../AnalyticsLoadingBar.vue'
|
||||
import {
|
||||
isNoDependentAnalyticsBreakdownValue,
|
||||
isUnknownAnalyticsBreakdownValue,
|
||||
} from '../breakdown.ts'
|
||||
import {
|
||||
buildAnalyticsTableColumns,
|
||||
getAnalyticsTableBreakdownColumnLabel,
|
||||
@@ -194,8 +259,10 @@ import { sortAnalyticsTableRows } from './analytics-table-sorting.ts'
|
||||
import type {
|
||||
AnalyticsTableColumnKey,
|
||||
AnalyticsTableMode,
|
||||
AnalyticsTableRow,
|
||||
AnalyticsTableSortDirectionValue,
|
||||
} from './analytics-table-types.ts'
|
||||
import ProjectCell from './ProjectCell.vue'
|
||||
import { useAnalyticsTableGraphSelection } from './use-analytics-table-graph-selection.ts'
|
||||
import { useAnalyticsTablePagination } from './use-analytics-table-pagination.ts'
|
||||
import { useAnalyticsTableRowCache } from './use-analytics-table-row-cache.ts'
|
||||
@@ -221,7 +288,15 @@ const {
|
||||
getRelevantAnalyticsDashboardStats,
|
||||
isLoading,
|
||||
versionNumbersById,
|
||||
versionProjectIdsById,
|
||||
versionProjectNamesById,
|
||||
projectNamesById,
|
||||
projectIconUrlsById,
|
||||
projectOrganizationIdsById,
|
||||
projectOrganizationNamesById,
|
||||
userNamesById,
|
||||
userAvatarUrlsById,
|
||||
dependentProjectTypesById,
|
||||
getVersionDisplayName,
|
||||
getVersionProjectName,
|
||||
} = injectAnalyticsDashboardContext()
|
||||
@@ -263,11 +338,10 @@ const showGraphDatasetSelection = computed(() =>
|
||||
? selectedProjectIdSet.value.size > 1
|
||||
: selectedBreakdowns.value.length > 0,
|
||||
)
|
||||
const showProjectVersionProjectColumn = computed(
|
||||
const includeDependentProjectTooltipContext = computed(
|
||||
() =>
|
||||
selectedBreakdownSet.value.has('version_id') &&
|
||||
!selectedBreakdownSet.value.has('project') &&
|
||||
selectedProjectIdSet.value.size > 1,
|
||||
selectedBreakdownSet.value.has('dependent_project_download') &&
|
||||
!selectedBreakdownSet.value.has('project'),
|
||||
)
|
||||
const includeDateColumn = computed(
|
||||
() =>
|
||||
@@ -314,9 +388,6 @@ const csvExportOptions = computed<OverflowMenuOption[]>(() => {
|
||||
},
|
||||
]
|
||||
})
|
||||
const projectNamesById = computed(
|
||||
() => new Map(projects.value.map((project) => [project.id, project.name])),
|
||||
)
|
||||
const hasAvailableProjects = computed(() => projects.value.length > 0)
|
||||
const analyticsPointCount = computed(() =>
|
||||
timeSlices.value.reduce((sum, slice) => sum + slice.length, 0),
|
||||
@@ -360,8 +431,12 @@ function buildTableRows(mode: AnalyticsTableMode) {
|
||||
timeSlices: timeSlices.value,
|
||||
selectedBreakdowns: selectedBreakdowns.value,
|
||||
selectedProjectIds: selectedProjectIdSet.value,
|
||||
selectedFilters: selectedFilters.value,
|
||||
dependentProjectTypesById: dependentProjectTypesById.value,
|
||||
includeDependentProjectTooltipContext: includeDependentProjectTooltipContext.value,
|
||||
relevantStats: relevantStats.value,
|
||||
projectNamesById: projectNamesById.value,
|
||||
userNamesById: userNamesById.value,
|
||||
getVersionDisplayName,
|
||||
getVersionProjectName,
|
||||
showTimeInBucketLabel: showTimeInBucketLabel.value,
|
||||
@@ -379,12 +454,86 @@ function buildColumns(includeDate: boolean) {
|
||||
selectedBreakdowns: selectedBreakdowns.value,
|
||||
selectedFilters: selectedFilters.value,
|
||||
showBreakdownColumn: showBreakdownColumn.value,
|
||||
showProjectVersionProjectColumn: showProjectVersionProjectColumn.value,
|
||||
formatMessage,
|
||||
getRelevantAnalyticsDashboardStats,
|
||||
})
|
||||
}
|
||||
|
||||
function getProjectIconUrl(projectId: string | undefined) {
|
||||
return projectId ? projectIconUrlsById.value.get(projectId) : undefined
|
||||
}
|
||||
|
||||
function getProjectOrganizationName(projectId: string | undefined) {
|
||||
return projectId ? projectOrganizationNamesById.value.get(projectId) : undefined
|
||||
}
|
||||
|
||||
function getProjectCellLabel(value: unknown) {
|
||||
return typeof value === 'string' ? value : String(value ?? '')
|
||||
}
|
||||
|
||||
function getUserAvatarUrl(userId: string | undefined) {
|
||||
return userId ? userAvatarUrlsById.value.get(userId) : undefined
|
||||
}
|
||||
|
||||
function getUserCellLabel(value: unknown) {
|
||||
return typeof value === 'string' ? value : String(value ?? '')
|
||||
}
|
||||
|
||||
function getUserPageHref(userId: string | undefined) {
|
||||
if (!userId) return undefined
|
||||
const username = userNamesById.value.get(userId) ?? userId
|
||||
|
||||
return `/user/${encodeURIComponent(username)}`
|
||||
}
|
||||
|
||||
function getProjectPageHref(projectId: string | undefined) {
|
||||
return projectId ? `/project/${encodeURIComponent(projectId)}` : undefined
|
||||
}
|
||||
|
||||
function getProjectOrganizationPageHref(projectId: string | undefined) {
|
||||
if (!projectId) return undefined
|
||||
const organizationId = projectOrganizationIdsById.value.get(projectId)
|
||||
if (!organizationId) return undefined
|
||||
|
||||
return `/organization/${encodeURIComponent(organizationId)}`
|
||||
}
|
||||
|
||||
function getVersionPageHref(versionId: string | undefined) {
|
||||
if (!versionId) return undefined
|
||||
const projectId = versionProjectIdsById.value.get(versionId)
|
||||
if (!projectId) return undefined
|
||||
|
||||
return `/project/${encodeURIComponent(projectId)}/version/${encodeURIComponent(versionId)}`
|
||||
}
|
||||
|
||||
function isMissingDependentProjectValue(value: string | undefined) {
|
||||
return isUnknownAnalyticsBreakdownValue(value) || isNoDependentAnalyticsBreakdownValue(value)
|
||||
}
|
||||
|
||||
function getDependentProjectTooltip(row: AnalyticsTableRow) {
|
||||
if (isNoDependentAnalyticsBreakdownValue(row.breakdownValues.dependent_project_download)) {
|
||||
return formatMessage(analyticsMessages.noDependentTooltip)
|
||||
}
|
||||
if (isUnknownAnalyticsBreakdownValue(row.breakdownValues.dependent_project_download)) {
|
||||
return formatMessage(analyticsMessages.unknown)
|
||||
}
|
||||
|
||||
const dependencyProjectIds = new Set(row.dependentOnProjectIds)
|
||||
if (row.dependentOnProjectId) {
|
||||
dependencyProjectIds.add(row.dependentOnProjectId)
|
||||
}
|
||||
|
||||
const dependencyProjectNames = [...dependencyProjectIds]
|
||||
.map((projectId) => projectNamesById.value.get(projectId) ?? projectId)
|
||||
.sort((left, right) => left.localeCompare(right))
|
||||
|
||||
return dependencyProjectNames.length > 0
|
||||
? formatMessage(analyticsChartMessages.dependentOnProjectTooltip, {
|
||||
project: dependencyProjectNames.join(', '),
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
|
||||
watch(
|
||||
activeColumns,
|
||||
(nextColumns) => {
|
||||
@@ -445,6 +594,9 @@ watch(
|
||||
selectedBreakdowns,
|
||||
selectedFilters,
|
||||
projects,
|
||||
dependentProjectTypesById,
|
||||
projectNamesById,
|
||||
userNamesById,
|
||||
versionNumbersById,
|
||||
versionProjectNamesById,
|
||||
],
|
||||
|
||||
+27
-2
@@ -7,6 +7,10 @@ import type {
|
||||
AnalyticsSelectedBreakdowns,
|
||||
} from '~/providers/analytics/analytics'
|
||||
|
||||
import {
|
||||
isNoDependentAnalyticsBreakdownValue,
|
||||
isUnknownAnalyticsBreakdownValue,
|
||||
} from '../breakdown'
|
||||
import { getAnalyticsTableMetricSortedGraphDatasetIds } from './analytics-table-sorting'
|
||||
import type { AnalyticsTableColumnKey, AnalyticsTableRow } from './analytics-table-types'
|
||||
|
||||
@@ -58,6 +62,9 @@ export function useAnalyticsTableGraphSelection({
|
||||
const filteredSelectableGraphDatasetIds = computed(() =>
|
||||
getAnalyticsTableSelectableGraphDatasetIds(filteredRows.value),
|
||||
)
|
||||
const excludedGraphDatasetIds = computed(() =>
|
||||
getAnalyticsTableExcludedGraphDatasetIds(sortedRows.value),
|
||||
)
|
||||
const sortedMetricGraphDatasetIds = computed(() =>
|
||||
getAnalyticsTableMetricSortedGraphDatasetIds(sortedRows.value, sortColumn.value, sortCollator),
|
||||
)
|
||||
@@ -65,7 +72,9 @@ export function useAnalyticsTableGraphSelection({
|
||||
const sortedMetricIds = sortedMetricGraphDatasetIds.value
|
||||
const defaultIds =
|
||||
sortedMetricIds.length > 0 ? sortedMetricIds : selectableGraphDatasetIds.value
|
||||
return defaultIds.slice(0, graphDatasetSelectionLimit)
|
||||
return defaultIds
|
||||
.filter((id) => !excludedGraphDatasetIds.value.has(id))
|
||||
.slice(0, graphDatasetSelectionLimit)
|
||||
})
|
||||
const tableSelectedGraphDatasetIds = computed<unknown[]>({
|
||||
get: () => selectedGraphDatasetIds.value,
|
||||
@@ -149,7 +158,9 @@ export function useAnalyticsTableGraphSelection({
|
||||
defaultGraphDatasetIds.value = nextShowGraphDatasetSelection
|
||||
? [...nextDefaultGraphDatasetIds]
|
||||
: []
|
||||
topGraphDatasetIds.value = nextShowGraphDatasetSelection ? [...nextTopGraphDatasetIds] : []
|
||||
topGraphDatasetIds.value = nextShowGraphDatasetSelection
|
||||
? nextTopGraphDatasetIds.filter((id) => !excludedGraphDatasetIds.value.has(id))
|
||||
: []
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
@@ -184,6 +195,20 @@ export function useAnalyticsTableGraphSelection({
|
||||
return Array.from(new Set(rows.map((row) => row.graphDatasetId)))
|
||||
}
|
||||
|
||||
function getAnalyticsTableExcludedGraphDatasetIds(rows: AnalyticsTableRow[]): Set<string> {
|
||||
return new Set(
|
||||
rows
|
||||
.filter((row) =>
|
||||
Object.values(row.breakdownValues).some(
|
||||
(value) =>
|
||||
isUnknownAnalyticsBreakdownValue(value) ||
|
||||
isNoDependentAnalyticsBreakdownValue(value),
|
||||
),
|
||||
)
|
||||
.map((row) => row.graphDatasetId),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
filteredSelectableGraphDatasetIds,
|
||||
tableSelectedGraphDatasetIds,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { formatAnalyticsDownloadSourceLabel, type FormatMessage } from './analyt
|
||||
|
||||
export const ALL_BREAKDOWN_VALUE = '__all__'
|
||||
export const UNKNOWN_BREAKDOWN_VALUE = '__unknown__'
|
||||
export const NO_DEPENDENT_BREAKDOWN_VALUE = '__no_dependent__'
|
||||
export const COMBINED_BREAKDOWN_LABEL_SEPARATOR = ' + '
|
||||
export const COMBINED_BREAKDOWN_DATASET_ID_PREFIX = 'breakdowns:'
|
||||
|
||||
@@ -41,6 +42,19 @@ export function getAnalyticsBreakdownValue(
|
||||
'reason' in point ? point.reason : undefined,
|
||||
UNKNOWN_BREAKDOWN_VALUE,
|
||||
)
|
||||
case 'user_id':
|
||||
return normalizeBreakdownValue('user_id' in point ? point.user_id : undefined)
|
||||
case 'dependent_project_download': {
|
||||
const dependentProjectId = normalizeBreakdownValue(
|
||||
'dependent_project_id' in point ? point.dependent_project_id : undefined,
|
||||
UNKNOWN_BREAKDOWN_VALUE,
|
||||
)
|
||||
const downloadReason = 'reason' in point ? point.reason?.trim().toLowerCase() : undefined
|
||||
return dependentProjectId === UNKNOWN_BREAKDOWN_VALUE &&
|
||||
(downloadReason === 'standalone' || downloadReason === 'update')
|
||||
? NO_DEPENDENT_BREAKDOWN_VALUE
|
||||
: dependentProjectId
|
||||
}
|
||||
case 'version_id':
|
||||
return normalizeBreakdownValue('version_id' in point ? point.version_id : undefined)
|
||||
case 'loader':
|
||||
@@ -94,16 +108,30 @@ export function getDownloadSourceLabel(value: string, formatMessage: FormatMessa
|
||||
return formatAnalyticsDownloadSourceLabel(value, formatMessage)
|
||||
}
|
||||
|
||||
export function isUnknownAnalyticsBreakdownValue(value: string | null | undefined): boolean {
|
||||
const normalized = value?.trim()
|
||||
if (!normalized) {
|
||||
return false
|
||||
}
|
||||
|
||||
const normalizedLowercase = normalized.toLowerCase()
|
||||
return (
|
||||
normalized === UNKNOWN_BREAKDOWN_VALUE ||
|
||||
normalizedLowercase === 'unknown' ||
|
||||
normalizedLowercase === 'other'
|
||||
)
|
||||
}
|
||||
|
||||
export function isNoDependentAnalyticsBreakdownValue(value: string | null | undefined): boolean {
|
||||
return value?.trim() === NO_DEPENDENT_BREAKDOWN_VALUE
|
||||
}
|
||||
|
||||
function normalizeBreakdownValue(
|
||||
value: string | undefined,
|
||||
fallback = ALL_BREAKDOWN_VALUE,
|
||||
): string {
|
||||
const normalized = value?.trim()
|
||||
const normalizedLowercase = normalized?.toLowerCase()
|
||||
if (
|
||||
fallback === UNKNOWN_BREAKDOWN_VALUE &&
|
||||
(normalizedLowercase === 'unknown' || normalizedLowercase === 'other')
|
||||
) {
|
||||
if (fallback === UNKNOWN_BREAKDOWN_VALUE && isUnknownAnalyticsBreakdownValue(normalized)) {
|
||||
return fallback
|
||||
}
|
||||
return normalized && normalized.length > 0 ? normalized : fallback
|
||||
|
||||
@@ -25,10 +25,25 @@
|
||||
|
||||
<template #option="{ category, option, selected }">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<template v-if="category.key === 'version_id'">
|
||||
<span
|
||||
v-if="category.key === 'user_id'"
|
||||
v-tooltip="option.label"
|
||||
class="flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-primary"
|
||||
:class="selected ? 'text-contrast' : 'text-primary'"
|
||||
>
|
||||
<img
|
||||
v-if="getUserAvatarUrl(option.value)"
|
||||
:src="getUserAvatarUrl(option.value)"
|
||||
:alt="option.label"
|
||||
class="h-6 w-6 rounded-full object-cover"
|
||||
/>
|
||||
<UserIcon v-else class="h-full w-full" />
|
||||
</span>
|
||||
<template
|
||||
v-for="metadata in getFilterOptionProjectMetadata(category.key, option.value)"
|
||||
:key="`${category.key}-${option.value}-${metadata.name}`"
|
||||
>
|
||||
<span
|
||||
v-for="metadata in getProjectVersionOptionProjectMetadata(option.value)"
|
||||
:key="`${option.value}-${metadata.name}`"
|
||||
v-tooltip="metadata.name"
|
||||
class="flex size-6 shrink-0 items-center justify-center overflow-hidden rounded text-primary"
|
||||
>
|
||||
@@ -42,6 +57,8 @@
|
||||
</span>
|
||||
</template>
|
||||
<span
|
||||
:ref="(element) => setFilterOptionLabelRef(category.key, option.value, element)"
|
||||
v-tooltip="getFilterOptionLabelTooltip(category.key, option.value, option.label)"
|
||||
class="min-w-0 truncate font-semibold leading-tight"
|
||||
:class="selected ? 'text-contrast' : 'text-primary'"
|
||||
>
|
||||
@@ -179,16 +196,24 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BoxIcon } from '@modrinth/assets'
|
||||
import { BoxIcon, UserIcon } from '@modrinth/assets'
|
||||
import {
|
||||
buildDependentsSearchFilters,
|
||||
DropdownFilterBar,
|
||||
type DropdownFilterBarCategory,
|
||||
type DropdownFilterBarOption,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
type ProjectType,
|
||||
Tabs,
|
||||
type TabsTab,
|
||||
type TabsValue,
|
||||
truncatedTooltip,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
|
||||
import { useFormattedCountries } from '@/composables/country.ts'
|
||||
import {
|
||||
@@ -258,6 +283,8 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const {
|
||||
hasProjectContext,
|
||||
projects,
|
||||
@@ -277,6 +304,9 @@ const {
|
||||
versionPublishedDatesById,
|
||||
versionProjectNamesById,
|
||||
versionProjectIconUrlsById,
|
||||
projectNamesById,
|
||||
userNamesById,
|
||||
userAvatarUrlsById,
|
||||
getVersionDisplayName,
|
||||
} = injectAnalyticsDashboardContext()
|
||||
const formattedCountries = useFormattedCountries()
|
||||
@@ -290,6 +320,17 @@ const gameVersionTypeTabs = computed<TabsTab[]>(() => [
|
||||
{ value: 'release', label: formatMessage(analyticsMessages.releaseTab) },
|
||||
{ value: 'all', label: formatMessage(analyticsMessages.allTab) },
|
||||
])
|
||||
const dependentProjectSearchProjectTypes: readonly ProjectType[] = [
|
||||
'mod',
|
||||
'modpack',
|
||||
'resourcepack',
|
||||
'shader',
|
||||
'datapack',
|
||||
'plugin',
|
||||
]
|
||||
const dependentProjectInitialSearchLimit = 100
|
||||
const dependentProjectQuerySearchLimit = 500
|
||||
const dependentProjectSearchDebounceMs = 250
|
||||
const resolvedAddLabel = computed(
|
||||
() => props.addLabel ?? formatMessage(analyticsMessages.addButton),
|
||||
)
|
||||
@@ -306,14 +347,16 @@ const projectStatusFilterOptions = computed<DropdownFilterBarOption[]>(() =>
|
||||
})),
|
||||
)
|
||||
const selectedProjectIdSet = computed(() => new Set(selectedProjectIds.value))
|
||||
const effectiveSelectedProjectCount = computed(
|
||||
() =>
|
||||
projects.value.filter(
|
||||
const effectiveSelectedProjectIds = computed(() =>
|
||||
projects.value
|
||||
.filter(
|
||||
(project) =>
|
||||
selectedProjectIdSet.value.has(project.id) &&
|
||||
doesProjectStatusMatchFilters(project.status, selectedFilters.value),
|
||||
).length,
|
||||
)
|
||||
.map((project) => project.id),
|
||||
)
|
||||
const effectiveSelectedProjectCount = computed(() => effectiveSelectedProjectIds.value.length)
|
||||
const showProjectVersionProjectIcons = computed(() => effectiveSelectedProjectCount.value > 1)
|
||||
const defaultSelectedBreakdown = computed(() =>
|
||||
getDefaultAnalyticsBreakdownPresets(selectedProjectIds.value),
|
||||
@@ -330,12 +373,72 @@ const projectVersionFilterOptions = shallowRef<ProjectVersionFilterOption[]>([])
|
||||
const projectVersionFilterOptionProjectMetadataById = shallowRef(
|
||||
new Map<string, ProjectVersionFilterOptionProjectMetadata[]>(),
|
||||
)
|
||||
const dependentProjectSearchInput = ref('')
|
||||
const dependentProjectSearchQuery = ref('')
|
||||
const draftSelectedFilters = ref<AnalyticsSelectedFilters>(
|
||||
cloneSelectedFilters(selectedFilters.value),
|
||||
)
|
||||
let selectedFiltersCommitRequestId = 0
|
||||
let projectVersionFilterOptionsCacheKey = ''
|
||||
let projectVersionFilterOptionProjectMetadataCacheKey = ''
|
||||
let dependentProjectSearchDebounceTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
const filterOptionLabelElements = new Map<string, HTMLElement>()
|
||||
const filterOptionLabelRefUpdateToken = ref(0)
|
||||
|
||||
const dependentProjectSearchFilters = computed(() =>
|
||||
buildDependentsSearchFilters(
|
||||
dependentProjectSearchProjectTypes,
|
||||
effectiveSelectedProjectIds.value,
|
||||
),
|
||||
)
|
||||
const dependentProjectSearchQueryValue = computed(() => dependentProjectSearchQuery.value.trim())
|
||||
const dependentProjectSearchResultLimit = computed(() =>
|
||||
dependentProjectSearchQueryValue.value.length > 0
|
||||
? dependentProjectQuerySearchLimit
|
||||
: dependentProjectInitialSearchLimit,
|
||||
)
|
||||
const selectedDependentProjectIds = computed(() => [
|
||||
...new Set([
|
||||
...selectedFilters.value.dependent_project_id,
|
||||
...draftSelectedFilters.value.dependent_project_id,
|
||||
]),
|
||||
])
|
||||
const { data: dependentProjectSearchResults, error: dependentProjectSearchError } = useQuery({
|
||||
queryKey: computed(() => [
|
||||
'analytics',
|
||||
'query-filter',
|
||||
'dependent-project-search',
|
||||
dependentProjectSearchFilters.value,
|
||||
dependentProjectSearchQueryValue.value,
|
||||
dependentProjectSearchResultLimit.value,
|
||||
]),
|
||||
queryFn: () =>
|
||||
client.labrinth.projects_v2.search({
|
||||
query: dependentProjectSearchQueryValue.value || undefined,
|
||||
new_filters: dependentProjectSearchFilters.value,
|
||||
limit: dependentProjectSearchResultLimit.value,
|
||||
index: 'downloads',
|
||||
}),
|
||||
enabled: computed(
|
||||
() =>
|
||||
effectiveSelectedProjectIds.value.length > 0 &&
|
||||
dependentProjectSearchFilters.value.length > 0,
|
||||
),
|
||||
placeholderData: (previousData) => previousData,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
const { data: selectedDependentProjects } = useQuery({
|
||||
queryKey: computed(() => [
|
||||
'analytics',
|
||||
'query-filter',
|
||||
'selected-dependent-projects',
|
||||
selectedDependentProjectIds.value,
|
||||
]),
|
||||
queryFn: () => client.labrinth.projects_v2.getMultiple(selectedDependentProjectIds.value),
|
||||
enabled: computed(() => selectedDependentProjectIds.value.length > 0),
|
||||
placeholderData: [],
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
const selectedFilterValue = computed<Record<string, string[]>>({
|
||||
get: () => getSelectedFilterBarValue(),
|
||||
@@ -374,6 +477,27 @@ watch(queryResetToken, () => {
|
||||
clearDownloadsThresholds()
|
||||
})
|
||||
|
||||
watch(dependentProjectSearchInput, (query) => {
|
||||
if (dependentProjectSearchDebounceTimeout) {
|
||||
clearTimeout(dependentProjectSearchDebounceTimeout)
|
||||
}
|
||||
|
||||
dependentProjectSearchDebounceTimeout = setTimeout(() => {
|
||||
dependentProjectSearchQuery.value = query.trim()
|
||||
dependentProjectSearchDebounceTimeout = null
|
||||
}, dependentProjectSearchDebounceMs)
|
||||
})
|
||||
|
||||
watch(dependentProjectSearchError, (error) => {
|
||||
if (!error) return
|
||||
|
||||
addNotification({
|
||||
title: 'Dependent projects failed to load',
|
||||
text: getDependentProjectSearchErrorMessage(error),
|
||||
type: 'error',
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
selectedFilters,
|
||||
(nextFilters, previousFilters) => {
|
||||
@@ -475,6 +599,12 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (dependentProjectSearchDebounceTimeout) {
|
||||
clearTimeout(dependentProjectSearchDebounceTimeout)
|
||||
}
|
||||
})
|
||||
|
||||
async function scheduleSelectedFiltersCommit() {
|
||||
const requestId = ++selectedFiltersCommitRequestId
|
||||
const nextFilters = cloneSelectedFilters(draftSelectedFilters.value)
|
||||
@@ -577,6 +707,40 @@ const filterCategories = computed<DropdownFilterBarCategory[]>(() => {
|
||||
label: formatMessage(analyticsBreakdownMessages.loader),
|
||||
options: withSelectedOptions('loader_type', loaderTypeFilterOptions.value),
|
||||
},
|
||||
{
|
||||
key: 'dependent_project_id',
|
||||
label: formatMessage(analyticsBreakdownMessages.dependentProjectDownload),
|
||||
searchable: true,
|
||||
disableLocalOptionsFilter: true,
|
||||
searchPlaceholder: formatMessage(analyticsMessages.searchDependentProjectsPlaceholder),
|
||||
emptyOptionsLabel: analyticsFilterOptionsEmptyLabel.value,
|
||||
emptySearchLabel: analyticsFilterOptionsEmptyLabel.value,
|
||||
onSearchQueryChange: setDependentProjectSearchInput,
|
||||
options: withSelectedOptions('dependent_project_id', dependentProjectFilterOptions.value),
|
||||
submenuClass: 'w-fit min-w-[18rem]',
|
||||
previewDropdownWidth: 'fit-content',
|
||||
previewDropdownMinWidth: '18rem',
|
||||
},
|
||||
{
|
||||
key: 'dependent_project_type',
|
||||
label: formatMessage(analyticsBreakdownMessages.dependentProjectType),
|
||||
options: withSelectedOptions(
|
||||
'dependent_project_type',
|
||||
dependentProjectTypeFilterOptions.value,
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'user_id',
|
||||
label: formatMessage(analyticsBreakdownMessages.members),
|
||||
searchable: memberFilterOptions.value.length > 6,
|
||||
searchPlaceholder: formatMessage(analyticsMessages.searchMembersPlaceholder),
|
||||
emptyOptionsLabel: analyticsFilterOptionsEmptyLabel.value,
|
||||
emptySearchLabel: analyticsFilterOptionsEmptyLabel.value,
|
||||
options: withSelectedOptions('user_id', memberFilterOptions.value),
|
||||
submenuClass: 'w-fit min-w-[14rem]',
|
||||
previewDropdownWidth: 'fit-content',
|
||||
previewDropdownMinWidth: '14rem',
|
||||
},
|
||||
)
|
||||
|
||||
return categories.filter((category) =>
|
||||
@@ -636,6 +800,16 @@ const downloadReasonFilterOptions = computed<DropdownFilterBarOption[]>(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const memberFilterOptions = computed<DropdownFilterBarOption[]>(() =>
|
||||
filterOptions.value.userIds
|
||||
.map((userId) => ({
|
||||
value: userId,
|
||||
label: getUserFilterOptionLabel(userId),
|
||||
searchTerms: [userId],
|
||||
}))
|
||||
.sort((left, right) => left.label.localeCompare(right.label)),
|
||||
)
|
||||
|
||||
const gameVersionFilterOptions = computed<DropdownFilterBarOption[]>(() =>
|
||||
filterOptions.value.gameVersions
|
||||
.filter((gameVersion) => {
|
||||
@@ -668,6 +842,45 @@ const loaderTypeFilterOptions = computed<DropdownFilterBarOption[]>(() =>
|
||||
.sort((left, right) => left.label.localeCompare(right.label)),
|
||||
)
|
||||
|
||||
const dependentProjectSearchResultsById = computed(
|
||||
() =>
|
||||
new Map(
|
||||
(dependentProjectSearchResults.value?.hits ?? []).map((project) => [
|
||||
project.project_id,
|
||||
project,
|
||||
]),
|
||||
),
|
||||
)
|
||||
const selectedDependentProjectsById = computed(
|
||||
() => new Map((selectedDependentProjects.value ?? []).map((project) => [project.id, project])),
|
||||
)
|
||||
|
||||
const dependentProjectFilterOptions = computed<DropdownFilterBarOption[]>(() => {
|
||||
const optionsById = new Map<string, DropdownFilterBarOption>()
|
||||
|
||||
for (const project of dependentProjectSearchResults.value?.hits ?? []) {
|
||||
optionsById.set(project.project_id, {
|
||||
value: project.project_id,
|
||||
label: project.title,
|
||||
searchTerms: [project.project_id, project.slug, project.author].filter(
|
||||
(term): term is string => Boolean(term),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return [...optionsById.values()]
|
||||
})
|
||||
|
||||
const dependentProjectTypeFilterOptions = computed<DropdownFilterBarOption[]>(() =>
|
||||
filterOptions.value.dependentProjectTypes
|
||||
.map((projectType) => ({
|
||||
value: projectType,
|
||||
label: getProjectTypeFilterOptionLabel(projectType),
|
||||
searchTerms: [projectType],
|
||||
}))
|
||||
.sort((left, right) => left.label.localeCompare(right.label)),
|
||||
)
|
||||
|
||||
function isAnalyticsFilterValueCategory(
|
||||
categoryKey: string,
|
||||
): categoryKey is AnalyticsFilterValueCategory {
|
||||
@@ -697,15 +910,77 @@ function getMissingSelectedOptionLabel(
|
||||
if (categoryKey === 'download_reason') {
|
||||
return getDownloadReasonFilterOptionLabel
|
||||
}
|
||||
if (categoryKey === 'user_id') {
|
||||
return getUserFilterOptionLabel
|
||||
}
|
||||
if (categoryKey === 'user_agent') {
|
||||
return (value) => getDownloadSourceLabel(value, formatMessage)
|
||||
}
|
||||
if (categoryKey === 'loader_type') {
|
||||
return getLoaderTypeFilterOptionLabel
|
||||
}
|
||||
if (categoryKey === 'dependent_project_id') {
|
||||
return getDependentProjectFilterOptionLabel
|
||||
}
|
||||
if (categoryKey === 'dependent_project_type') {
|
||||
return getProjectTypeFilterOptionLabel
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getUserAvatarUrl(userId: string): string | undefined {
|
||||
return userAvatarUrlsById.value.get(userId)
|
||||
}
|
||||
|
||||
function setFilterOptionLabelRef(
|
||||
categoryKey: string,
|
||||
optionValue: string,
|
||||
element: Element | ComponentPublicInstance | null,
|
||||
) {
|
||||
const key = getFilterOptionLabelElementKey(categoryKey, optionValue)
|
||||
if (element instanceof HTMLElement) {
|
||||
if (filterOptionLabelElements.get(key) === element) {
|
||||
return
|
||||
}
|
||||
|
||||
filterOptionLabelElements.set(key, element)
|
||||
filterOptionLabelRefUpdateToken.value++
|
||||
return
|
||||
}
|
||||
|
||||
if (filterOptionLabelElements.delete(key)) {
|
||||
filterOptionLabelRefUpdateToken.value++
|
||||
}
|
||||
}
|
||||
|
||||
function getFilterOptionLabelTooltip(
|
||||
categoryKey: string,
|
||||
optionValue: string,
|
||||
label: string,
|
||||
): string | undefined {
|
||||
void filterOptionLabelRefUpdateToken.value
|
||||
|
||||
return truncatedTooltip(
|
||||
filterOptionLabelElements.get(getFilterOptionLabelElementKey(categoryKey, optionValue)),
|
||||
label,
|
||||
)
|
||||
}
|
||||
|
||||
function getFilterOptionLabelElementKey(categoryKey: string, optionValue: string) {
|
||||
return `${categoryKey}\x1f${optionValue}`
|
||||
}
|
||||
|
||||
function getFilterOptionProjectMetadata(categoryKey: string, optionValue: string) {
|
||||
if (categoryKey === 'version_id') {
|
||||
return getProjectVersionOptionProjectMetadata(optionValue)
|
||||
}
|
||||
if (categoryKey === 'dependent_project_id') {
|
||||
return getDependentProjectOptionProjectMetadata(optionValue)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function getProjectVersionOptionProjectMetadata(versionId: string) {
|
||||
if (!showProjectVersionProjectIcons.value) {
|
||||
return []
|
||||
@@ -714,6 +989,24 @@ function getProjectVersionOptionProjectMetadata(versionId: string) {
|
||||
return projectVersionFilterOptionProjectMetadataById.value.get(versionId) ?? []
|
||||
}
|
||||
|
||||
function getDependentProjectOptionProjectMetadata(projectId: string) {
|
||||
const searchResult = dependentProjectSearchResultsById.value.get(projectId)
|
||||
const selectedProject = selectedDependentProjectsById.value.get(projectId)
|
||||
const name =
|
||||
searchResult?.title ??
|
||||
selectedProject?.title ??
|
||||
projectNamesById.value.get(projectId) ??
|
||||
projectId
|
||||
const iconUrl = searchResult?.icon_url ?? selectedProject?.icon_url
|
||||
|
||||
return [
|
||||
{
|
||||
name,
|
||||
...(iconUrl ? { iconUrl } : {}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function getCountryFilterOptionLabel(countryCode: string): string {
|
||||
const normalizedCode = countryCode.trim().toUpperCase()
|
||||
if (normalizedCode === 'XX') {
|
||||
@@ -731,10 +1024,51 @@ function getLoaderTypeFilterOptionLabel(loaderType: string): string {
|
||||
return formatAnalyticsLoaderLabel(loaderType, formatMessage)
|
||||
}
|
||||
|
||||
function getProjectTypeFilterOptionLabel(projectType: string): string {
|
||||
return formatProjectType(projectType)
|
||||
}
|
||||
|
||||
function getDependentProjectFilterOptionLabel(projectId: string): string {
|
||||
return (
|
||||
dependentProjectSearchResultsById.value.get(projectId)?.title ??
|
||||
selectedDependentProjectsById.value.get(projectId)?.title ??
|
||||
projectNamesById.value.get(projectId) ??
|
||||
projectId
|
||||
)
|
||||
}
|
||||
|
||||
function getDownloadReasonFilterOptionLabel(reason: string): string {
|
||||
return formatAnalyticsDownloadReasonLabel(reason, formatMessage)
|
||||
}
|
||||
|
||||
function getUserFilterOptionLabel(userId: string): string {
|
||||
return userNamesById.value.get(userId) ?? userId
|
||||
}
|
||||
|
||||
function setDependentProjectSearchInput(query: string) {
|
||||
dependentProjectSearchInput.value = query
|
||||
}
|
||||
|
||||
function getDependentProjectSearchErrorMessage(error: unknown): string {
|
||||
if (error && typeof error === 'object') {
|
||||
const dataDescription = (error as { data?: { description?: unknown } }).data?.description
|
||||
if (typeof dataDescription === 'string' && dataDescription.length > 0) {
|
||||
return dataDescription
|
||||
}
|
||||
|
||||
const message = (error as { message?: unknown }).message
|
||||
if (typeof message === 'string' && message.length > 0) {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof error === 'string' && error.length > 0) {
|
||||
return error
|
||||
}
|
||||
|
||||
return 'Please try searching again or changing the selected projects.'
|
||||
}
|
||||
|
||||
function getDateTimestamp(date: string | undefined): number | undefined {
|
||||
if (!date) {
|
||||
return undefined
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:placeholder="formatMessage(analyticsMessages.selectProjects)"
|
||||
:no-options-message="noProjectsMessage"
|
||||
:searchable="projectOptions.length > 6"
|
||||
fuzzy-search
|
||||
:max-tag-rows="1"
|
||||
:trigger-class="analyticsQueryChipTriggerClass"
|
||||
fit-content
|
||||
@@ -79,7 +80,7 @@
|
||||
@keydown.enter.stop
|
||||
@keydown.space.stop
|
||||
>
|
||||
<LayersIcon
|
||||
<UserIcon
|
||||
class="h-5 w-5 shrink-0 text-primary"
|
||||
:class="isUserProjectsOptionSelected ? 'text-contrast' : 'text-primary'"
|
||||
/>
|
||||
@@ -211,6 +212,7 @@
|
||||
:placeholder="formatMessage(analyticsMessages.selectProjects)"
|
||||
:no-options-message="noProjectsMessage"
|
||||
:searchable="projectOptions.length > 6"
|
||||
fuzzy-search
|
||||
:max-tag-rows="1"
|
||||
checkbox-position="right"
|
||||
show-selection-actions
|
||||
@@ -290,7 +292,7 @@
|
||||
@keydown.enter.stop
|
||||
@keydown.space.stop
|
||||
>
|
||||
<LayersIcon
|
||||
<UserIcon
|
||||
class="h-5 w-5 shrink-0 text-primary"
|
||||
:class="isUserProjectsOptionSelected ? 'text-contrast' : 'text-primary'"
|
||||
/>
|
||||
@@ -449,6 +451,7 @@ import {
|
||||
ClockIcon,
|
||||
FolderOpenIcon,
|
||||
LayersIcon,
|
||||
UserIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
@@ -499,7 +502,7 @@ import {
|
||||
import TimeFramePicker from './TimeframePicker.vue'
|
||||
|
||||
const QUERY_BUILDER_DROPDOWN_MAX_HEIGHT = 500
|
||||
const QUERY_BUILDER_DROPDOWN_MIN_WIDTH = '12rem'
|
||||
const QUERY_BUILDER_DROPDOWN_MIN_WIDTH = '14rem'
|
||||
const analyticsQueryChipTriggerClass = 'h-10 '
|
||||
const analyticsQueryAddFilterButtonClass = '!h-10 max-w-full !w-max !px-3.5 flex !gap-2'
|
||||
const projectOptionCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
|
||||
@@ -544,7 +547,7 @@ function getProjectOption(
|
||||
return {
|
||||
value: project.id,
|
||||
label: project.name,
|
||||
searchTerms: groupTitle ? [groupTitle] : undefined,
|
||||
searchTerms: [project.id, groupTitle].filter((term): term is string => Boolean(term)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,15 +1011,34 @@ const breakdownOptions = computed<MultiSelectOption<Exclude<AnalyticsBreakdownPr
|
||||
value: 'game_version',
|
||||
label: formatAnalyticsBreakdownLabel('game_version', formatMessage),
|
||||
},
|
||||
{
|
||||
value: 'dependent_project_download',
|
||||
label: formatAnalyticsBreakdownLabel('dependent_project_download', formatMessage),
|
||||
},
|
||||
{
|
||||
value: 'user_id',
|
||||
label: formatAnalyticsBreakdownLabel('user_id', formatMessage),
|
||||
},
|
||||
)
|
||||
|
||||
return options.map((option) => ({
|
||||
...option,
|
||||
disabled: hasReachedBreakdownLimit && !selectedBreakdownSet.has(option.value),
|
||||
}))
|
||||
return options.map((option) => {
|
||||
const isSelected = selectedBreakdownSet.has(option.value)
|
||||
return {
|
||||
...option,
|
||||
disabled:
|
||||
!isSelected && (hasReachedBreakdownLimit || !canSelectBreakdownOption(option.value)),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
function canSelectBreakdownOption(breakdown: Exclude<AnalyticsBreakdownPreset, 'none'>): boolean {
|
||||
return getAnalyticsBreakdownPresetsForProjectSelection(
|
||||
[...selectedBreakdownValue.value, breakdown],
|
||||
selectedProjectIds.value,
|
||||
).includes(breakdown)
|
||||
}
|
||||
|
||||
function getBreakdownOptionLabel(breakdown: Exclude<AnalyticsBreakdownPreset, 'none'>): string {
|
||||
return (
|
||||
breakdownOptions.value.find((option) => option.value === breakdown)?.label ??
|
||||
@@ -1136,6 +1158,17 @@ function withBreakdownFields(
|
||||
downloads.push('reason')
|
||||
}
|
||||
break
|
||||
case 'user_id':
|
||||
if (includesStat(breakdownStats, 'revenue') && includesStat(enabledStats, 'revenue')) {
|
||||
revenue.push('user_id')
|
||||
}
|
||||
break
|
||||
case 'dependent_project_download':
|
||||
if (includesStat(breakdownStats, 'downloads') && includesStat(enabledStats, 'downloads')) {
|
||||
downloads.push('dependent_project_id')
|
||||
downloads.push('reason')
|
||||
}
|
||||
break
|
||||
case 'version_id':
|
||||
if (includesStat(breakdownStats, 'downloads') && includesStat(enabledStats, 'downloads')) {
|
||||
downloads.push('version_id')
|
||||
@@ -1264,6 +1297,12 @@ function buildMetricFilters(
|
||||
filters.game_version,
|
||||
),
|
||||
loader: getFilterValuesForStat('loader_type', 'downloads', enabledStats, filters.loader_type),
|
||||
dependent_project_id: getFilterValuesForStat(
|
||||
'dependent_project_id',
|
||||
'downloads',
|
||||
enabledStats,
|
||||
filters.dependent_project_id,
|
||||
),
|
||||
},
|
||||
playtime: {
|
||||
country: getFilterValuesForStat('country', 'playtime', enabledStats, filters.country),
|
||||
@@ -1281,7 +1320,9 @@ function buildMetricFilters(
|
||||
),
|
||||
loader: getFilterValuesForStat('loader_type', 'playtime', enabledStats, filters.loader_type),
|
||||
},
|
||||
revenue: {},
|
||||
revenue: {
|
||||
user_id: getFilterValuesForStat('user_id', 'revenue', enabledStats, filters.user_id),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1295,6 +1336,15 @@ const fetchRequest = computed<Labrinth.Analytics.v3.FetchRequest>(() => {
|
||||
|
||||
const bucketBy = withBreakdownFields(selectedBreakdowns.value, selectedFilters.value)
|
||||
const filterBy = buildMetricFilters(selectedBreakdowns.value, selectedFilters.value)
|
||||
if (
|
||||
includesStat(
|
||||
getEnabledAnalyticsStatsForState(selectedBreakdowns.value, selectedFilters.value),
|
||||
'revenue',
|
||||
) &&
|
||||
!bucketBy.revenue.includes('user_id')
|
||||
) {
|
||||
bucketBy.revenue.push('user_id')
|
||||
}
|
||||
const filteredProjectIds = getProjectIdsMatchingStatusFilter(
|
||||
selectedProjectIds.value,
|
||||
projectStatusById.value,
|
||||
|
||||
+68
-6
@@ -2,6 +2,7 @@ import type {
|
||||
AnalyticsBreakdownPreset,
|
||||
AnalyticsDashboardStat,
|
||||
AnalyticsQueryFilterCategory,
|
||||
AnalyticsSelectedBreakdowns,
|
||||
AnalyticsSelectedFilters,
|
||||
} from '~/providers/analytics/analytics'
|
||||
|
||||
@@ -13,6 +14,10 @@ export type AnalyticsDashboardDimension =
|
||||
| 'monetization'
|
||||
| 'user_agent'
|
||||
| 'download_reason'
|
||||
| 'user_id'
|
||||
| 'dependent_project_download'
|
||||
| 'dependent_project_id'
|
||||
| 'dependent_project_type'
|
||||
| 'game_version'
|
||||
| 'loader_type'
|
||||
|
||||
@@ -23,9 +28,12 @@ export const FILTER_VALUE_CATEGORIES: Exclude<AnalyticsQueryFilterCategory, 'pro
|
||||
'monetization',
|
||||
'user_agent',
|
||||
'download_reason',
|
||||
'user_id',
|
||||
'version_id',
|
||||
'game_version',
|
||||
'loader_type',
|
||||
'dependent_project_id',
|
||||
'dependent_project_type',
|
||||
]
|
||||
|
||||
const ANALYTICS_DASHBOARD_STAT_ORDER: AnalyticsDashboardStat[] = [
|
||||
@@ -45,8 +53,12 @@ const ANALYTICS_STATS_BY_DIMENSION: Record<
|
||||
monetization: ['views', 'downloads'],
|
||||
user_agent: ['downloads'],
|
||||
download_reason: ['downloads'],
|
||||
user_id: ['revenue'],
|
||||
dependent_project_download: ['downloads'],
|
||||
dependent_project_type: ['downloads'],
|
||||
game_version: ['downloads', 'playtime'],
|
||||
loader_type: ['downloads', 'playtime'],
|
||||
dependent_project_id: ['downloads'],
|
||||
project_status: ANALYTICS_DASHBOARD_STAT_ORDER,
|
||||
}
|
||||
|
||||
@@ -60,6 +72,8 @@ const ANALYTICS_DIMENSION_BY_BREAKDOWN: Record<
|
||||
monetization: 'monetization',
|
||||
user_agent: 'user_agent',
|
||||
download_reason: 'download_reason',
|
||||
user_id: 'user_id',
|
||||
dependent_project_download: 'dependent_project_download',
|
||||
version_id: 'version_id',
|
||||
loader: 'loader_type',
|
||||
game_version: 'game_version',
|
||||
@@ -74,9 +88,12 @@ const ANALYTICS_DIMENSION_BY_FILTER_CATEGORY: Record<
|
||||
monetization: 'monetization',
|
||||
user_agent: 'user_agent',
|
||||
download_reason: 'download_reason',
|
||||
user_id: 'user_id',
|
||||
version_id: 'version_id',
|
||||
game_version: 'game_version',
|
||||
loader_type: 'loader_type',
|
||||
dependent_project_id: 'dependent_project_id',
|
||||
dependent_project_type: 'dependent_project_type',
|
||||
}
|
||||
|
||||
const ANALYTICS_FILTER_CATEGORY_BY_BREAKDOWN: Record<
|
||||
@@ -89,6 +106,8 @@ const ANALYTICS_FILTER_CATEGORY_BY_BREAKDOWN: Record<
|
||||
monetization: 'monetization',
|
||||
user_agent: 'user_agent',
|
||||
download_reason: 'download_reason',
|
||||
user_id: 'user_id',
|
||||
dependent_project_download: null,
|
||||
version_id: 'version_id',
|
||||
loader: 'loader_type',
|
||||
game_version: 'game_version',
|
||||
@@ -286,6 +305,33 @@ export function getAnalyticsStatsForBreakdowns(
|
||||
return stats
|
||||
}
|
||||
|
||||
export function getAnalyticsBreakdownsWithSharedStats(
|
||||
breakdowns: AnalyticsBreakdownInput,
|
||||
): AnalyticsSelectedBreakdowns {
|
||||
const normalizedBreakdowns = normalizeAnalyticsBreakdowns(breakdowns)
|
||||
const compatibleBreakdowns: AnalyticsSelectedBreakdowns = []
|
||||
let sharedStats: readonly AnalyticsDashboardStat[] | null = null
|
||||
|
||||
for (const breakdown of normalizedBreakdowns) {
|
||||
const breakdownStats = getAnalyticsStatsForBreakdown(breakdown)
|
||||
if (sharedStats === null) {
|
||||
compatibleBreakdowns.push(breakdown)
|
||||
sharedStats = breakdownStats
|
||||
continue
|
||||
}
|
||||
|
||||
const nextSharedStats = intersectAnalyticsStats(sharedStats, breakdownStats)
|
||||
if (nextSharedStats.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
compatibleBreakdowns.push(breakdown)
|
||||
sharedStats = nextSharedStats
|
||||
}
|
||||
|
||||
return compatibleBreakdowns
|
||||
}
|
||||
|
||||
export function getAnalyticsStatsForFilterCategory(
|
||||
category: AnalyticsQueryFilterCategory,
|
||||
): readonly AnalyticsDashboardStat[] {
|
||||
@@ -331,12 +377,20 @@ export function getVisibleAnalyticsFilterCategoriesForState(
|
||||
breakdowns: AnalyticsBreakdownInput,
|
||||
filters: AnalyticsSelectedFilters,
|
||||
): readonly Exclude<AnalyticsQueryFilterCategory, 'project'>[] {
|
||||
return FILTER_VALUE_CATEGORIES.filter((category) =>
|
||||
haveAnalyticsStatOverlap(
|
||||
const normalizedBreakdowns = normalizeAnalyticsBreakdowns(breakdowns)
|
||||
return FILTER_VALUE_CATEGORIES.filter((category) => {
|
||||
if (
|
||||
category === 'dependent_project_type' &&
|
||||
!normalizedBreakdowns.includes('dependent_project_download')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return haveAnalyticsStatOverlap(
|
||||
getAnalyticsStatsForFilterScope(breakdowns, filters, category),
|
||||
getAnalyticsStatsForFilterCategory(category),
|
||||
),
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function sanitizeAnalyticsSelectedFilters(
|
||||
@@ -345,6 +399,7 @@ export function sanitizeAnalyticsSelectedFilters(
|
||||
): AnalyticsSelectedFilters {
|
||||
const nextFilters = cloneSelectedFilters(filters)
|
||||
let availableStats = [...getAnalyticsStatsForBreakdowns(breakdowns)]
|
||||
const normalizedBreakdowns = normalizeAnalyticsBreakdowns(breakdowns)
|
||||
|
||||
for (const category of FILTER_VALUE_CATEGORIES) {
|
||||
if (filters[category].length === 0) {
|
||||
@@ -352,7 +407,11 @@ export function sanitizeAnalyticsSelectedFilters(
|
||||
}
|
||||
|
||||
const categoryStats = getAnalyticsStatsForFilterCategory(category)
|
||||
if (!haveAnalyticsStatOverlap(availableStats, categoryStats)) {
|
||||
if (
|
||||
!haveAnalyticsStatOverlap(availableStats, categoryStats) ||
|
||||
(category === 'dependent_project_type' &&
|
||||
!normalizedBreakdowns.includes('dependent_project_download'))
|
||||
) {
|
||||
nextFilters[category] = []
|
||||
continue
|
||||
}
|
||||
@@ -371,9 +430,12 @@ export function cloneSelectedFilters(filters: AnalyticsSelectedFilters): Analyti
|
||||
monetization: [...filters.monetization],
|
||||
user_agent: [...filters.user_agent],
|
||||
download_reason: [...filters.download_reason],
|
||||
user_id: [...filters.user_id],
|
||||
version_id: [...filters.version_id],
|
||||
game_version: [...filters.game_version],
|
||||
loader_type: [...filters.loader_type],
|
||||
dependent_project_id: [...filters.dependent_project_id],
|
||||
dependent_project_type: [...filters.dependent_project_type],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +518,7 @@ export function normalizeSelectedValues(
|
||||
.map((value) => value.trim().toLowerCase())
|
||||
.filter(isProjectStatusFilterValue)
|
||||
}
|
||||
if (categoryKey === 'loader_type') {
|
||||
if (categoryKey === 'loader_type' || categoryKey === 'dependent_project_type') {
|
||||
return Array.from(
|
||||
new Set(
|
||||
selectedValues
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
areStringArraysEqual,
|
||||
buildAnalyticsQueryBuilderRouteQuery,
|
||||
getAnalyticsBreakdownPresetsForProjectSelection,
|
||||
hasAnalyticsAllProjectSelectionQuery,
|
||||
hasAnalyticsQueryBuilderRouteChange,
|
||||
readAnalyticsGraphState,
|
||||
readAnalyticsQueryBuilderState,
|
||||
@@ -54,6 +55,7 @@ export interface UseAnalyticsRouteSyncOptions {
|
||||
graph: AnalyticsGraphRefs
|
||||
availableProjectIds: Ref<string[]>
|
||||
defaultProjectIds: Ref<string[]>
|
||||
areProjectsLoaded: Ref<boolean>
|
||||
sanitizeSelectedFilters: (
|
||||
breakdowns: readonly AnalyticsBreakdownPreset[],
|
||||
filters: AnalyticsSelectedFilters,
|
||||
@@ -61,8 +63,14 @@ export interface UseAnalyticsRouteSyncOptions {
|
||||
}
|
||||
|
||||
export function useAnalyticsRouteSync(options: UseAnalyticsRouteSyncOptions) {
|
||||
const { queryBuilder, graph, availableProjectIds, defaultProjectIds, sanitizeSelectedFilters } =
|
||||
options
|
||||
const {
|
||||
queryBuilder,
|
||||
graph,
|
||||
availableProjectIds,
|
||||
defaultProjectIds,
|
||||
areProjectsLoaded,
|
||||
sanitizeSelectedFilters,
|
||||
} = options
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -113,6 +121,10 @@ export function useAnalyticsRouteSync(options: UseAnalyticsRouteSyncOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
if (hasAnalyticsAllProjectSelectionQuery(route.query) && !areProjectsLoaded.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextRouteQuery = buildAnalyticsQueryBuilderRouteQuery(
|
||||
route.query,
|
||||
getSelectedAnalyticsQueryBuilderState(),
|
||||
@@ -147,6 +159,10 @@ export function useAnalyticsRouteSync(options: UseAnalyticsRouteSyncOptions) {
|
||||
}
|
||||
|
||||
function applyRouteQueryToState(nextQuery: LocationQuery) {
|
||||
if (hasAnalyticsAllProjectSelectionQuery(nextQuery) && !areProjectsLoaded.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextQueryState = readAnalyticsQueryBuilderState(
|
||||
nextQuery,
|
||||
availableProjectIds.value,
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div
|
||||
class="shadow-card mx-auto flex w-full max-w-[27rem] flex-col gap-6 rounded-2xl border border-solid border-button-bg bg-surface-3 !p-6"
|
||||
>
|
||||
<h1
|
||||
class="mx-auto my-0 flex w-full justify-center text-center text-2xl font-semibold text-contrast"
|
||||
>
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h1>
|
||||
|
||||
<section v-if="requiresDob" class="flex flex-col gap-2.5">
|
||||
<label class="text-md font-semibold text-contrast" for="create-account-dob">
|
||||
{{ formatMessage(messages.dateOfBirthLabel) }}
|
||||
</label>
|
||||
<DatePicker
|
||||
id="create-account-dob"
|
||||
v-model="dateOfBirthModel"
|
||||
wrapper-class="w-full"
|
||||
min-date="1900-01-01"
|
||||
:max-date="maxInputDate"
|
||||
:default-view-date="defaultDobViewDate"
|
||||
preserve-day
|
||||
:placeholder="formatMessage(messages.dateOfBirthPlaceholder)"
|
||||
/>
|
||||
<Admonition :type="'info'">
|
||||
<template #header>
|
||||
<div class="-mb-3 -mt-1 flex flex-col gap-0 text-sm font-normal leading-normal">
|
||||
<div>
|
||||
{{ formatMessage(messages.infoPanelText) }}
|
||||
</div>
|
||||
<a
|
||||
class="w-fit text-link underline"
|
||||
:href="SOURCE_CODE_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ formatMessage(messages.relevantSourceCodeText) }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</Admonition>
|
||||
</section>
|
||||
|
||||
<section class="flex flex-col gap-2.5">
|
||||
<label class="text-md font-semibold text-contrast" for="create-account-username">
|
||||
{{ formatMessage(messages.usernameOptionalLabel) }}
|
||||
</label>
|
||||
<StyledInput
|
||||
id="create-account-username"
|
||||
v-model="usernameModel"
|
||||
type="text"
|
||||
:placeholder="formatMessage(messages.usernamePlaceholder)"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section v-if="globals?.captcha_enabled" class="flex flex-col gap-2.5">
|
||||
<label class="text-md font-semibold text-contrast">{{
|
||||
formatMessage(messages.securityCheckLabel)
|
||||
}}</label>
|
||||
<HCaptcha v-if="globals?.captcha_enabled" :ref="onSetCaptchaRef" v-model="tokenModel" />
|
||||
</section>
|
||||
|
||||
<div
|
||||
class="flex gap-2.5 rounded-2xl border border-solid border-surface-5 bg-surface-3 transition-all hover:brightness-110"
|
||||
>
|
||||
<Checkbox
|
||||
v-model="subscribeModel"
|
||||
class="p-3 text-left leading-snug text-primary transition-all"
|
||||
:label="formatMessage(messages.subscribeLabel)"
|
||||
:description="formatMessage(messages.subscribeLabel)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="!w-full font-bold"
|
||||
:disabled="globals?.captcha_enabled ? !tokenModel : false"
|
||||
@click="onCompleteSignUpClick"
|
||||
>
|
||||
{{ formatMessage(messages.completeSignUpButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RightArrowIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import HCaptcha from '@/components/ui/auth/HCaptcha.vue'
|
||||
|
||||
interface AuthGlobals {
|
||||
captcha_enabled?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
globals?: AuthGlobals | null
|
||||
requiresDob?: boolean
|
||||
onCompleteSignUp?: () => void
|
||||
onSetCaptchaRef?: ((captchaRef: unknown) => void) | undefined
|
||||
}
|
||||
|
||||
const {
|
||||
globals = null,
|
||||
requiresDob = true,
|
||||
onCompleteSignUp = () => {},
|
||||
onSetCaptchaRef = undefined,
|
||||
} = defineProps<Props>()
|
||||
|
||||
const SOURCE_CODE_URL =
|
||||
'https://github.com/modrinth/code/blob/main/apps/frontend/src/components/ui/auth/CreateAccount.vue'
|
||||
|
||||
const dateOfBirthModel = defineModel<string | null>('dateOfBirth', { default: '' })
|
||||
const usernameModel = defineModel<string>('username', { default: '' })
|
||||
const tokenModel = defineModel<string>('token', { default: '' })
|
||||
const subscribeModel = defineModel<boolean>('subscribe', { default: false })
|
||||
|
||||
const maxInputDate = computed(() => `${new Date().getFullYear()}-12-31`)
|
||||
|
||||
const defaultDobViewDate = computed(() => `${new Date().getFullYear()}-01-01`)
|
||||
|
||||
const maxBirthDate = computed(() => {
|
||||
const date = new Date()
|
||||
date.setFullYear(date.getFullYear() - 13)
|
||||
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day}`
|
||||
})
|
||||
|
||||
const getBirthYear = (dateOfBirth: string | null): number | null => {
|
||||
if (!dateOfBirth) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [yearPart = ''] = dateOfBirth.split('-')
|
||||
const year = Number(yearPart)
|
||||
return Number.isInteger(year) ? year : null
|
||||
}
|
||||
|
||||
const isDateOfBirthMissing = computed(() => requiresDob && !dateOfBirthModel.value)
|
||||
|
||||
const isDateOfBirthYearZero = computed(() => {
|
||||
if (!requiresDob || !dateOfBirthModel.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return getBirthYear(dateOfBirthModel.value) === 0
|
||||
})
|
||||
|
||||
const isUnder13 = computed(
|
||||
() => requiresDob && !!dateOfBirthModel.value && dateOfBirthModel.value > maxBirthDate.value,
|
||||
)
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
function onCompleteSignUpClick() {
|
||||
if (isDateOfBirthMissing.value) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.dateOfBirthRequiredTitle),
|
||||
text: formatMessage(messages.dateOfBirthRequiredText),
|
||||
type: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (isDateOfBirthYearZero.value) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.dateOfBirthInvalidTitle),
|
||||
text: formatMessage(messages.dateOfBirthInvalidText),
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (isUnder13.value) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.ageRequirementWarningTitle),
|
||||
text: formatMessage(messages.under13HelperText),
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
onCompleteSignUp()
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'auth.create-account.title',
|
||||
defaultMessage: 'Create an Account',
|
||||
},
|
||||
dateOfBirthLabel: {
|
||||
id: 'auth.create-account.date-of-birth.label',
|
||||
defaultMessage: 'Date of birth',
|
||||
},
|
||||
dateOfBirthPlaceholder: {
|
||||
id: 'auth.create-account.date-of-birth.placeholder',
|
||||
defaultMessage: 'Select your date of birth',
|
||||
},
|
||||
dateOfBirthRequiredTitle: {
|
||||
id: 'auth.create-account.date-of-birth.required.title',
|
||||
defaultMessage: 'Date of birth required',
|
||||
},
|
||||
dateOfBirthRequiredText: {
|
||||
id: 'auth.create-account.date-of-birth.required.text',
|
||||
defaultMessage: 'Please enter your date of birth before continuing.',
|
||||
},
|
||||
dateOfBirthInvalidTitle: {
|
||||
id: 'auth.create-account.date-of-birth.invalid.title',
|
||||
defaultMessage: 'Invalid date of birth',
|
||||
},
|
||||
dateOfBirthInvalidText: {
|
||||
id: 'auth.create-account.date-of-birth.invalid.text',
|
||||
defaultMessage: 'Please enter a valid date of birth. Year cannot be 0000.',
|
||||
},
|
||||
under13HelperText: {
|
||||
id: 'auth.create-account.date-of-birth.under13-helper',
|
||||
defaultMessage: 'You do not meet the age requirement to create an account at Modrinth.',
|
||||
},
|
||||
ageRequirementWarningTitle: {
|
||||
id: 'auth.create-account.age-requirement.warning-title',
|
||||
defaultMessage: 'Age requirement',
|
||||
},
|
||||
infoPanelText: {
|
||||
id: 'auth.create-account.info-panel.text',
|
||||
defaultMessage:
|
||||
'We do not store your date of birth. We only use it during sign-up to validate COPPA compliance.',
|
||||
},
|
||||
relevantSourceCodeText: {
|
||||
id: 'auth.create-account.info-panel.source-code-link',
|
||||
defaultMessage: 'Relevant source code',
|
||||
},
|
||||
usernameOptionalLabel: {
|
||||
id: 'auth.create-account.username.label',
|
||||
defaultMessage: 'Username',
|
||||
},
|
||||
usernamePlaceholder: {
|
||||
id: 'auth.create-account.username.placeholder',
|
||||
defaultMessage: 'Enter username',
|
||||
},
|
||||
securityCheckLabel: {
|
||||
id: 'auth.create-account.security-check.label',
|
||||
defaultMessage: 'Security check',
|
||||
},
|
||||
subscribeLabel: {
|
||||
id: 'auth.create-account.subscribe.label',
|
||||
defaultMessage: 'Keep me updated on the cool things Modrinth is working on via email',
|
||||
},
|
||||
completeSignUpButton: {
|
||||
id: 'auth.create-account.complete-sign-up',
|
||||
defaultMessage: 'Complete sign up',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
+21
-4
@@ -47,17 +47,34 @@ defineExpose({
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
<style>
|
||||
.h-captcha {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border: 2px solid var(--color-button-bg);
|
||||
height: 78px;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
margin: -1px;
|
||||
.h-captcha iframe {
|
||||
transform: scale(1.33);
|
||||
transform-origin: center;
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.h-captcha {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.h-captcha iframe {
|
||||
transform: scale(1.03);
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<div class="contents">
|
||||
<ConfirmModal
|
||||
ref="removePasskeyModal"
|
||||
:title="formatMessage(messages.removePasskeyConfirmTitle)"
|
||||
:description="
|
||||
formatMessage(messages.removePasskeyConfirmDescription, { name: passkeyToRemove?.name })
|
||||
"
|
||||
:proceed-label="formatMessage(commonMessages.removeButton)"
|
||||
@proceed="removePasskey()"
|
||||
/>
|
||||
|
||||
<NewModal
|
||||
ref="managePasskeyModal"
|
||||
width="600px"
|
||||
:header="formatMessage(messages.managePasskeyTitle)"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div v-if="passkeysLoading" class="flex items-center gap-2 text-secondary">
|
||||
<SpinnerIcon class="animate-spin" />
|
||||
{{ formatMessage(messages.managePasskeyModalLoading) }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<p v-if="passkeys.length === 0" class="m-0 text-secondary">
|
||||
{{ formatMessage(messages.managePasskeyModalNoPasskeys) }}
|
||||
</p>
|
||||
<div
|
||||
v-for="passkey in passkeys"
|
||||
:key="passkey.id"
|
||||
class="universal-card recessed flex items-center justify-between"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<strong>{{ passkey.name }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span v-tooltip="formatDateTime(passkey.created_at)">
|
||||
{{
|
||||
formatMessage(messages.managePasskeyModalAdded, {
|
||||
ago: formatRelativeTime(passkey.created_at),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
⋅
|
||||
<span v-if="passkey.last_used" v-tooltip="formatDateTime(passkey.last_used)">
|
||||
{{
|
||||
formatMessage(messages.managePasskeyModalLastUsed, {
|
||||
ago: formatRelativeTime(passkey.last_used),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ formatMessage(messages.managePasskeyModalNeverUsed) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.renameButton)"
|
||||
@click="
|
||||
() => {
|
||||
passkeyToRename = { ...passkey }
|
||||
renamePasskeyModal?.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.removeButton)"
|
||||
@click="
|
||||
() => {
|
||||
passkeyToRemove = passkey
|
||||
removePasskeyModal?.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="input-group self-end">
|
||||
<ButtonStyled>
|
||||
<button @click="registerPasskey()">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.managePasskeyAddPasskey) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button @click="managePasskeyModal?.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.closeButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
<NewModal
|
||||
ref="addPasskeyModal"
|
||||
width="500px"
|
||||
:header="formatMessage(messages.managePasskeyAddPasskey)"
|
||||
>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-md font-semibold text-contrast">
|
||||
{{ formatMessage(messages.passkeyNameLabel) }}
|
||||
</div>
|
||||
<StyledInput
|
||||
id="passkey-name"
|
||||
v-model="pendingPasskeyName"
|
||||
:maxlength="255"
|
||||
type="text"
|
||||
:placeholder="formatMessage(messages.passkeyNamePlaceholder)"
|
||||
@keyup.enter="pendingPasskeyName && finishRegisterPasskey()"
|
||||
/>
|
||||
<div class="label__description">
|
||||
{{ formatMessage(messages.passkeyNameDescription) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2.5">
|
||||
<ButtonStyled>
|
||||
<button @click="addPasskeyModal?.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!pendingPasskeyName" @click="finishRegisterPasskey()">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.managePasskeyAddPasskey) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
<NewModal
|
||||
ref="renamePasskeyModal"
|
||||
width="500px"
|
||||
:header="formatMessage(messages.renamePasskeyModalHeader)"
|
||||
>
|
||||
<div v-if="passkeyToRename" class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-md font-semibold text-contrast">
|
||||
{{ formatMessage(messages.passkeyNameLabel) }}
|
||||
</div>
|
||||
<StyledInput
|
||||
id="passkey-rename"
|
||||
v-model="passkeyToRenameName"
|
||||
:maxlength="255"
|
||||
type="text"
|
||||
:placeholder="formatMessage(messages.passkeyNamePlaceholder)"
|
||||
@keyup.enter="passkeyToRenameName && renamePasskey()"
|
||||
/>
|
||||
<div class="label__description">
|
||||
{{ formatMessage(messages.passkeyNameDescription) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2.5">
|
||||
<ButtonStyled>
|
||||
<button @click="renamePasskeyModal?.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!passkeyToRenameName" @click="renamePasskey()">
|
||||
<SaveIcon />
|
||||
{{ formatMessage(commonMessages.saveButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
<div class="adjacent-input mb-0">
|
||||
<label for="manage-passkeys">
|
||||
<span class="label__title">{{ formatMessage(messages.managePasskeyTitle) }}</span>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.managePasskeyDescription)
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<ButtonStyled>
|
||||
<button id="manage-passkeys" @click="showPasskeyModal">
|
||||
<UserKeyIcon /> {{ formatMessage(messages.managePasskeyTitle) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
EditIcon,
|
||||
PlusIcon,
|
||||
SaveIcon,
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
UserKeyIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
NewModal,
|
||||
StyledInput,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
|
||||
import { createPasskeyCredential } from '~/helpers/passkey.ts'
|
||||
|
||||
const { labrinth } = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
function notifyError(err: unknown) {
|
||||
addNotification({
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: err instanceof Error ? err.message : String(err),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
managePasskeyTitle: {
|
||||
id: 'settings.account.security.passkey.title',
|
||||
defaultMessage: 'Manage passkeys',
|
||||
},
|
||||
managePasskeyDescription: {
|
||||
id: 'settings.account.security.passkey.description',
|
||||
defaultMessage: 'Manage your registered passkeys, or add a new one.',
|
||||
},
|
||||
managePasskeyAddPasskey: {
|
||||
id: 'settings.account.security.passkey.add',
|
||||
defaultMessage: 'Add passkey',
|
||||
},
|
||||
managePasskeyModalLoading: {
|
||||
id: 'settings.account.security.passkey.modal.loading',
|
||||
defaultMessage: 'Loading passkeys…',
|
||||
},
|
||||
managePasskeyModalNoPasskeys: {
|
||||
id: 'settings.account.security.passkey.modal.no-passkeys',
|
||||
defaultMessage: 'You do not have any passkeys registered.',
|
||||
},
|
||||
managePasskeyModalAdded: {
|
||||
id: 'settings.account.security.passkey.modal.added',
|
||||
defaultMessage: 'Added {ago}',
|
||||
},
|
||||
managePasskeyModalLastUsed: {
|
||||
id: 'settings.account.security.passkey.modal.last-used',
|
||||
defaultMessage: 'Last used {ago}',
|
||||
},
|
||||
managePasskeyModalNeverUsed: {
|
||||
id: 'settings.account.security.passkey.modal.never-used',
|
||||
defaultMessage: 'Never used',
|
||||
},
|
||||
passkeyNameLabel: {
|
||||
id: 'settings.account.security.passkey.add-modal.name.label',
|
||||
defaultMessage: 'Name',
|
||||
},
|
||||
passkeyNameDescription: {
|
||||
id: 'settings.account.security.passkey.add-modal.name.description',
|
||||
defaultMessage:
|
||||
'Make sure to pick something memorable, so you can identify this passkey later.',
|
||||
},
|
||||
passkeyNamePlaceholder: {
|
||||
id: 'settings.account.security.passkey.add-modal.name.placeholder',
|
||||
defaultMessage: 'My passkey',
|
||||
},
|
||||
renamePasskeyModalHeader: {
|
||||
id: 'settings.account.security.passkey.rename-modal.header',
|
||||
defaultMessage: 'Rename passkey',
|
||||
},
|
||||
removePasskeyConfirmTitle: {
|
||||
id: 'settings.account.security.passkey.remove.title',
|
||||
defaultMessage: 'Are you sure you want to remove this passkey?',
|
||||
},
|
||||
removePasskeyConfirmDescription: {
|
||||
id: 'settings.account.security.passkey.remove.description',
|
||||
defaultMessage:
|
||||
'This will permanently remove the passkey "{name}". You will no longer be able to sign in with it.',
|
||||
},
|
||||
})
|
||||
|
||||
const removePasskeyModal = useTemplateRef<InstanceType<typeof ConfirmModal>>('removePasskeyModal')
|
||||
const managePasskeyModal = useTemplateRef<InstanceType<typeof NewModal>>('managePasskeyModal')
|
||||
const addPasskeyModal = useTemplateRef<InstanceType<typeof NewModal>>('addPasskeyModal')
|
||||
const renamePasskeyModal = useTemplateRef<InstanceType<typeof NewModal>>('renamePasskeyModal')
|
||||
|
||||
const pendingPasskey = ref<Labrinth.Auth.v2.PasskeyRegisterFinishRequest | null>(null)
|
||||
const passkeyToRemove = ref<Labrinth.Auth.v2.Passkey | null>(null)
|
||||
const passkeyToRename = ref<Labrinth.Auth.v2.Passkey | null>(null)
|
||||
const pendingPasskeyName = computed({
|
||||
get: () => pendingPasskey.value?.name ?? '',
|
||||
set: (name) => {
|
||||
if (pendingPasskey.value) {
|
||||
pendingPasskey.value.name = name
|
||||
}
|
||||
},
|
||||
})
|
||||
const passkeyToRenameName = computed({
|
||||
get: () => passkeyToRename.value?.name ?? '',
|
||||
set: (name) => {
|
||||
if (passkeyToRename.value) {
|
||||
passkeyToRename.value.name = name
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const passkeys = ref<Labrinth.Auth.v2.Passkey[]>([])
|
||||
const passkeysLoading = ref(false)
|
||||
|
||||
async function fetchPasskeys() {
|
||||
passkeysLoading.value = true
|
||||
try {
|
||||
passkeys.value = await labrinth.auth_v2.listPasskeys()
|
||||
} catch (err) {
|
||||
notifyError(err)
|
||||
}
|
||||
passkeysLoading.value = false
|
||||
}
|
||||
|
||||
async function showPasskeyModal() {
|
||||
managePasskeyModal.value?.show()
|
||||
await fetchPasskeys()
|
||||
}
|
||||
|
||||
async function registerPasskey() {
|
||||
startLoading()
|
||||
try {
|
||||
const res = await labrinth.auth_v2.registerPasskeyStart()
|
||||
|
||||
const credential = await createPasskeyCredential(res.options.publicKey)
|
||||
pendingPasskey.value = {
|
||||
flow: res.flow,
|
||||
credential: credential,
|
||||
name: '',
|
||||
}
|
||||
|
||||
addPasskeyModal.value?.show()
|
||||
} catch (err) {
|
||||
notifyError(err)
|
||||
}
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
async function finishRegisterPasskey() {
|
||||
if (!pendingPasskey.value) return
|
||||
|
||||
startLoading()
|
||||
try {
|
||||
const passkey = await labrinth.auth_v2.registerPasskeyFinish(pendingPasskey.value)
|
||||
passkeys.value.unshift(passkey)
|
||||
pendingPasskey.value = null
|
||||
addPasskeyModal.value?.hide()
|
||||
} catch (err) {
|
||||
notifyError(err)
|
||||
}
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
async function renamePasskey() {
|
||||
if (!passkeyToRename.value) return
|
||||
const { id, name } = passkeyToRename.value
|
||||
|
||||
startLoading()
|
||||
try {
|
||||
await labrinth.auth_v2.renamePasskey(id, { name })
|
||||
const existing = passkeys.value.find((passkey) => passkey.id === id)
|
||||
if (existing) {
|
||||
existing.name = name
|
||||
}
|
||||
passkeyToRename.value = null
|
||||
renamePasskeyModal.value?.hide()
|
||||
} catch (err) {
|
||||
notifyError(err)
|
||||
}
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
async function removePasskey() {
|
||||
if (!passkeyToRemove.value) return
|
||||
const { id } = passkeyToRemove.value
|
||||
|
||||
startLoading()
|
||||
try {
|
||||
await labrinth.auth_v2.deletePasskey(id)
|
||||
passkeys.value = passkeys.value.filter((passkey) => passkey.id !== id)
|
||||
passkeyToRemove.value = null
|
||||
} catch (err) {
|
||||
notifyError(err)
|
||||
}
|
||||
stopLoading()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div v-if="subtleLauncherRedirectUri">
|
||||
<iframe
|
||||
:src="subtleLauncherRedirectUri"
|
||||
class="fixed left-0 top-0 z-[9999] m-0 h-full w-full border-0 p-0"
|
||||
></iframe>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="universal-card mx-auto flex w-full max-w-[27rem] flex-col gap-6 border border-solid border-surface-5 !p-6"
|
||||
>
|
||||
<template v-if="flow && !subtleLauncherRedirectUri">
|
||||
<div class="flex flex-col items-end gap-4">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<label for="two-factor-code">
|
||||
<span class="label__title">{{ formatMessage(messages.twoFactorCodeLabel) }}</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(messages.twoFactorCodeLabelDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<StyledInput
|
||||
id="two-factor-code"
|
||||
v-model="twoFactorCodeModel"
|
||||
:maxlength="11"
|
||||
inputmode="numeric"
|
||||
:placeholder="formatMessage(messages.twoFactorCodeInputPlaceholder)"
|
||||
autocomplete="one-time-code"
|
||||
@keyup.enter="onTwoFactorSignIn()"
|
||||
/>
|
||||
</div>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="onTwoFactorSignIn()">
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="text-center text-2xl font-semibold text-contrast">
|
||||
{{ formatMessage(messages.signInWithLabel) }}
|
||||
</div>
|
||||
|
||||
<section class="grid grid-cols-1 gap-2.5 sm:grid-cols-2">
|
||||
<ButtonStyled v-for="provider in oauthProviders" :key="provider.id">
|
||||
<a
|
||||
class="relative w-full !justify-center overflow-visible !shadow-none"
|
||||
:class="{
|
||||
'!border !border-[var(--color-green)]': lastSignInProvider === provider.id,
|
||||
}"
|
||||
:href="getAuthUrl(provider.id, redirectTarget)"
|
||||
:aria-label="
|
||||
formatMessage(messages.continueWithProvider, { provider: provider.name })
|
||||
"
|
||||
@click="onOAuthProviderClick(provider.id)"
|
||||
>
|
||||
<component :is="provider.icon" />
|
||||
<span>{{ provider.name }}</span>
|
||||
<span
|
||||
v-if="lastSignInProvider === provider.id"
|
||||
class="oauth-provider-last-sign-in-badge"
|
||||
>
|
||||
{{ formatMessage(messages.lastSignInLabel) }}
|
||||
</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="relative w-full !justify-center overflow-visible !shadow-none sm:col-span-2"
|
||||
:class="{ '!border !border-[var(--color-green)]': lastSignInProvider === 'passkey' }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="onPasskeySignIn"
|
||||
@keydown.enter="onPasskeySignIn"
|
||||
>
|
||||
<UserKeyIcon />
|
||||
<span class="ml-1">{{ formatMessage(messages.continueWithPasskey) }}</span>
|
||||
<span
|
||||
v-if="lastSignInProvider === 'passkey'"
|
||||
class="oauth-provider-last-sign-in-badge"
|
||||
>
|
||||
{{ formatMessage(messages.lastSignInLabel) }}
|
||||
</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<div class="h-px w-full bg-surface-5"></div>
|
||||
|
||||
<section class="mx-auto flex w-full flex-col gap-2.5">
|
||||
<label for="email" hidden>{{ formatMessage(commonMessages.emailUsernameLabel) }}</label>
|
||||
<StyledInput
|
||||
id="email"
|
||||
v-model="emailModel"
|
||||
:icon="MailIcon"
|
||||
type="text"
|
||||
inputmode="email"
|
||||
autocomplete="username"
|
||||
:placeholder="formatMessage(commonMessages.emailUsernameLabel)"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
|
||||
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
|
||||
<StyledInput
|
||||
id="password"
|
||||
v-model="passwordModel"
|
||||
:icon="KeyIcon"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
:placeholder="formatMessage(commonMessages.passwordLabel)"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
|
||||
<HCaptcha
|
||||
v-if="globals?.captcha_enabled && emailModel && passwordModel"
|
||||
:ref="onSetCaptchaRef"
|
||||
v-model="tokenModel"
|
||||
/>
|
||||
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="!w-full"
|
||||
:disabled="globals?.captcha_enabled ? !tokenModel : false"
|
||||
@click="onPasswordSignIn()"
|
||||
>
|
||||
{{ formatMessage(messages.continueWithEmail) }} <RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-center gap-2.5 !text-base">
|
||||
<NuxtLink
|
||||
class="text-link"
|
||||
:to="{
|
||||
path: '/auth/reset-password',
|
||||
query: routeQuery,
|
||||
}"
|
||||
>
|
||||
{{ formatMessage(messages.forgotPasswordLabel) }}
|
||||
</NuxtLink>
|
||||
<div class="h-1.5 w-1.5 rounded-full bg-surface-5" />
|
||||
<NuxtLink
|
||||
class="inline text-link"
|
||||
:to="{
|
||||
path: '/auth/sign-up',
|
||||
query: routeQuery,
|
||||
}"
|
||||
>
|
||||
{{ formatMessage(messages.createAccountLabel) }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DiscordColorIcon,
|
||||
GitHubColorIcon,
|
||||
GitLabColorIcon,
|
||||
GoogleColorIcon,
|
||||
KeyIcon,
|
||||
MailIcon,
|
||||
MicrosoftColorIcon,
|
||||
RightArrowIcon,
|
||||
SteamColorIcon,
|
||||
UserKeyIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { ButtonStyled, commonMessages, defineMessages, StyledInput, useVIntl } from '@modrinth/ui'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import type { LocationQuery } from 'vue-router'
|
||||
|
||||
import HCaptcha from '@/components/ui/auth/HCaptcha.vue'
|
||||
import {
|
||||
getAuthUrl,
|
||||
LAST_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY,
|
||||
PENDING_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY,
|
||||
} from '@/composables/auth.ts'
|
||||
|
||||
const oauthProviders = [
|
||||
{ id: 'discord', name: 'Discord', icon: DiscordColorIcon },
|
||||
{ id: 'github', name: 'GitHub', icon: GitHubColorIcon },
|
||||
{ id: 'microsoft', name: 'Microsoft', icon: MicrosoftColorIcon },
|
||||
{ id: 'google', name: 'Google', icon: GoogleColorIcon },
|
||||
{ id: 'steam', name: 'Steam', icon: SteamColorIcon },
|
||||
{ id: 'gitlab', name: 'GitLab', icon: GitLabColorIcon },
|
||||
] as const
|
||||
|
||||
type AuthProvider = (typeof oauthProviders)[number]['id'] | 'passkey'
|
||||
|
||||
interface AuthGlobals {
|
||||
captcha_enabled?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface Props {
|
||||
subtleLauncherRedirectUri?: string
|
||||
flow?: string
|
||||
redirectTarget?: string
|
||||
routeQuery?: LocationQuery
|
||||
globals?: AuthGlobals | null
|
||||
onPasswordSignIn?: () => void
|
||||
onTwoFactorSignIn?: () => void
|
||||
onPasskeySignIn?: () => void
|
||||
onSetCaptchaRef?: ((captchaRef: unknown) => void) | undefined
|
||||
}
|
||||
|
||||
const {
|
||||
subtleLauncherRedirectUri = '',
|
||||
flow = '',
|
||||
redirectTarget = '',
|
||||
routeQuery = {},
|
||||
globals = null,
|
||||
onPasswordSignIn = () => {},
|
||||
onTwoFactorSignIn = () => {},
|
||||
onPasskeySignIn = () => {},
|
||||
onSetCaptchaRef = undefined,
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emailModel = defineModel<string>('email', { default: '' })
|
||||
const passwordModel = defineModel<string>('password', { default: '' })
|
||||
const tokenModel = defineModel<string>('token', { default: '' })
|
||||
const twoFactorCodeModel = defineModel<string>('twoFactorCode', { default: '' })
|
||||
|
||||
const lastSignInOAuthProvider = useStorage<AuthProvider | null>(
|
||||
LAST_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY,
|
||||
null,
|
||||
undefined,
|
||||
{ initOnMounted: true },
|
||||
)
|
||||
const pendingSignInOAuthProvider = useStorage<AuthProvider | null>(
|
||||
PENDING_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY,
|
||||
null,
|
||||
undefined,
|
||||
{ initOnMounted: true },
|
||||
)
|
||||
const lastSignInProvider = computed(() => lastSignInOAuthProvider.value)
|
||||
const onOAuthProviderClick = (provider: AuthProvider) => {
|
||||
pendingSignInOAuthProvider.value = provider
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
forgotPasswordLabel: {
|
||||
id: 'auth.sign-in.forgot-password',
|
||||
defaultMessage: 'Forgot password',
|
||||
},
|
||||
noAccountLabel: {
|
||||
id: 'auth.sign-in.no-account',
|
||||
defaultMessage: "Don't have an account?",
|
||||
},
|
||||
createAccountLabel: {
|
||||
id: 'auth.sign-in.create-account',
|
||||
defaultMessage: 'Sign up',
|
||||
},
|
||||
signInWithLabel: {
|
||||
id: 'auth.sign-in.sign-in-with',
|
||||
defaultMessage: 'Sign into Modrinth',
|
||||
},
|
||||
twoFactorCodeInputPlaceholder: {
|
||||
id: 'auth.sign-in.2fa.placeholder',
|
||||
defaultMessage: 'Enter code...',
|
||||
},
|
||||
twoFactorCodeLabel: {
|
||||
id: 'auth.sign-in.2fa.label',
|
||||
defaultMessage: 'Two-factor authentication',
|
||||
},
|
||||
twoFactorCodeLabelDescription: {
|
||||
id: 'auth.sign-in.2fa.description',
|
||||
defaultMessage:
|
||||
'Enter the 6-digit code from your authenticator app, or one of your backup codes.',
|
||||
},
|
||||
continueWithProvider: {
|
||||
id: 'auth.continue-with-provider',
|
||||
defaultMessage: 'Continue with {provider}',
|
||||
},
|
||||
continueWithEmail: {
|
||||
id: 'auth.sign-in.continue-with-email',
|
||||
defaultMessage: 'Continue with Email',
|
||||
},
|
||||
lastSignInLabel: {
|
||||
id: 'auth.sign-in.last-sign-in',
|
||||
defaultMessage: 'Last used',
|
||||
},
|
||||
continueWithPasskey: {
|
||||
id: 'auth.sign-in.continue-with-passkey',
|
||||
defaultMessage: 'Continue with passkey',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.oauth-provider-last-sign-in-badge {
|
||||
position: absolute;
|
||||
top: -0.75rem;
|
||||
right: 0.25rem;
|
||||
z-index: 1;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--surface-3);
|
||||
color: var(--color-green);
|
||||
border: 1px solid var(--color-green);
|
||||
padding: 0.125rem 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.oauth-provider-last-sign-in-badge::before {
|
||||
content: '';
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background-color: var(--color-green-highlight);
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div
|
||||
class="universal-card flex w-full max-w-[27rem] flex-col gap-6 border border-solid border-surface-5 !p-6"
|
||||
>
|
||||
<div class="mx-auto text-center text-2xl font-semibold text-contrast">
|
||||
{{ formatMessage(messages.signUpWithTitle) }}
|
||||
</div>
|
||||
<section class="flex flex-col gap-2.5">
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="!shadow-none"
|
||||
:href="getAuthUrl('google', redirectTarget)"
|
||||
@click="onOAuthProviderClick('google')"
|
||||
>
|
||||
<GoogleColorIcon />
|
||||
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Google' }) }}</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="!shadow-none"
|
||||
:href="getAuthUrl('microsoft', redirectTarget)"
|
||||
@click="onOAuthProviderClick('microsoft')"
|
||||
>
|
||||
<MicrosoftColorIcon />
|
||||
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Microsoft' }) }}</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="!shadow-none"
|
||||
:href="getAuthUrl('discord', redirectTarget)"
|
||||
@click="onOAuthProviderClick('discord')"
|
||||
>
|
||||
<DiscordColorIcon />
|
||||
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Discord' }) }}</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<template v-if="showOtherOptions">
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="!shadow-none"
|
||||
:href="getAuthUrl('github', redirectTarget)"
|
||||
@click="onOAuthProviderClick('github')"
|
||||
>
|
||||
<GitHubColorIcon />
|
||||
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'GitHub' }) }}</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="!shadow-none"
|
||||
:href="getAuthUrl('gitlab', redirectTarget)"
|
||||
@click="onOAuthProviderClick('gitlab')"
|
||||
>
|
||||
<GitLabColorIcon />
|
||||
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'GitLab' }) }}</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a
|
||||
class="!shadow-none"
|
||||
:href="getAuthUrl('steam', redirectTarget)"
|
||||
@click="onOAuthProviderClick('steam')"
|
||||
>
|
||||
<SteamColorIcon />
|
||||
<span>{{ formatMessage(messages.continueWithProvider, { provider: 'Steam' }) }}</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<button
|
||||
class="mx-auto -mb-3 bg-transparent pt-1 text-center text-base font-semibold text-secondary transition-all hover:text-primary"
|
||||
@click="onToggleOtherOptions()"
|
||||
>
|
||||
{{
|
||||
showOtherOptions
|
||||
? formatMessage(messages.showFewerOptions)
|
||||
: formatMessage(messages.showOtherOptions)
|
||||
}}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div class="h-px w-full bg-surface-5"></div>
|
||||
|
||||
<section class="flex flex-col gap-2.5">
|
||||
<label for="email" hidden>{{ formatMessage(commonMessages.emailLabel) }}</label>
|
||||
<StyledInput
|
||||
id="email"
|
||||
v-model="emailModel"
|
||||
:icon="MailIcon"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
:placeholder="formatMessage(commonMessages.emailLabel)"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
|
||||
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
|
||||
<StyledInput
|
||||
id="password"
|
||||
v-model="passwordModel"
|
||||
:icon="KeyIcon"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
:placeholder="formatMessage(commonMessages.passwordLabel)"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="!w-full"
|
||||
:disabled="!emailModel || !passwordModel"
|
||||
@click="onContinueWithEmail()"
|
||||
>
|
||||
{{ formatMessage(messages.continueWithEmail) }} <RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<p v-if="!routeQuery.launcher" class="m-0 text-center">
|
||||
<IntlFormatted :message-id="messages.legalDisclaimer">
|
||||
<template #terms-link="{ children }">
|
||||
<NuxtLink to="/legal/terms" class="text-link">
|
||||
<component :is="() => children" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template #privacy-policy-link="{ children }">
|
||||
<NuxtLink to="/legal/privacy" class="text-link">
|
||||
<component :is="() => children" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
|
||||
<div class="mx-auto flex flex-wrap items-center justify-start gap-2 text-center">
|
||||
{{ formatMessage(messages.alreadyHaveAccountLabel) }}
|
||||
<NuxtLink
|
||||
class="mr-1 text-link"
|
||||
:to="{
|
||||
path: '/auth/sign-in',
|
||||
query: routeQuery,
|
||||
}"
|
||||
>
|
||||
{{ formatMessage(commonMessages.signInButton) }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DiscordColorIcon,
|
||||
GitHubColorIcon,
|
||||
GitLabColorIcon,
|
||||
GoogleColorIcon,
|
||||
KeyIcon,
|
||||
MailIcon,
|
||||
MicrosoftColorIcon,
|
||||
RightArrowIcon,
|
||||
SteamColorIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
IntlFormatted,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { LocationQuery } from 'vue-router'
|
||||
|
||||
import { getAuthUrl, PENDING_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY } from '@/composables/auth.ts'
|
||||
|
||||
type AuthProvider = 'discord' | 'google' | 'github' | 'gitlab' | 'steam' | 'microsoft'
|
||||
|
||||
interface Props {
|
||||
redirectTarget?: string
|
||||
showOtherOptions?: boolean
|
||||
routeQuery?: LocationQuery
|
||||
onToggleOtherOptions?: () => void
|
||||
onContinueWithEmail?: () => void
|
||||
}
|
||||
|
||||
const {
|
||||
redirectTarget = '',
|
||||
showOtherOptions = false,
|
||||
routeQuery = {},
|
||||
onToggleOtherOptions = () => {},
|
||||
onContinueWithEmail = () => {},
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emailModel = defineModel<string>('email', { default: '' })
|
||||
const passwordModel = defineModel<string>('password', { default: '' })
|
||||
|
||||
const pendingSignInOAuthProvider = useStorage<AuthProvider | null>(
|
||||
PENDING_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY,
|
||||
null,
|
||||
)
|
||||
const onOAuthProviderClick = (provider: AuthProvider) => {
|
||||
pendingSignInOAuthProvider.value = provider
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
signUpWithTitle: {
|
||||
id: 'auth.sign-up.title.sign-up-with',
|
||||
defaultMessage: 'Create an Account',
|
||||
},
|
||||
continueWithProvider: {
|
||||
id: 'auth.continue-with-provider',
|
||||
defaultMessage: 'Continue with {provider}',
|
||||
},
|
||||
legalDisclaimer: {
|
||||
id: 'auth.sign-up.legal-dislaimer',
|
||||
defaultMessage:
|
||||
"By creating an account, you agree to Modrinth's <terms-link>Terms</terms-link> and <privacy-policy-link>Privacy Policy</privacy-policy-link>.",
|
||||
},
|
||||
alreadyHaveAccountLabel: {
|
||||
id: 'auth.sign-up.sign-in-option.title',
|
||||
defaultMessage: 'Already have an account?',
|
||||
},
|
||||
continueWithEmail: {
|
||||
id: 'auth.sign-up.continue-with-email',
|
||||
defaultMessage: 'Continue with Email',
|
||||
},
|
||||
showFewerOptions: {
|
||||
id: 'auth.sign-up.show-fewer-options',
|
||||
defaultMessage: 'Show fewer options',
|
||||
},
|
||||
showOtherOptions: {
|
||||
id: 'auth.sign-up.show-other-options',
|
||||
defaultMessage: 'Show other options',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -89,6 +89,8 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { generateUrlSlug } from '~/utils/slugs'
|
||||
|
||||
import CreateLimitAlert from './CreateLimitAlert.vue'
|
||||
|
||||
const router = useNativeRouter()
|
||||
@@ -148,7 +150,7 @@ async function createOrganization(): Promise<void> {
|
||||
const value = {
|
||||
name: name.value.trim(),
|
||||
description: description.value.trim(),
|
||||
slug: slug.value.trim().replace(/ +/g, ''),
|
||||
slug: slug.value.trim(),
|
||||
}
|
||||
|
||||
const result: any = await useBaseFetch('organization', {
|
||||
@@ -183,12 +185,7 @@ function hide(): void {
|
||||
|
||||
function updateSlug(): void {
|
||||
if (!manualSlug.value) {
|
||||
slug.value = name.value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')
|
||||
.replaceAll(/[^a-zA-Z0-9!@$()`.+,_"-]/g, '')
|
||||
.replaceAll(/--+/gm, '-')
|
||||
slug.value = generateUrlSlug(name.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,8 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { computed, defineAsyncComponent, h } from 'vue'
|
||||
|
||||
import { generateUrlSlug } from '~/utils/slugs'
|
||||
|
||||
import CreateLimitAlert from './CreateLimitAlert.vue'
|
||||
|
||||
type ProjectTypes = 'server' | 'project'
|
||||
@@ -164,9 +166,7 @@ defineExpose({
|
||||
show,
|
||||
})
|
||||
|
||||
const auth = (await useAuth()) as Ref<{
|
||||
user: { id: string; username: string; avatar_url: string } | null
|
||||
}>
|
||||
const auth = await useAuth()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
@@ -461,12 +461,7 @@ async function show(event?: MouseEvent, options?: ShowOptions) {
|
||||
|
||||
function updatedName() {
|
||||
if (!manualSlug.value) {
|
||||
slug.value = name.value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')
|
||||
.replaceAll(/[^a-zA-Z0-9!@$()`.+,_"-]/g, '')
|
||||
.replaceAll(/--+/gm, '-')
|
||||
slug.value = generateUrlSlug(name.value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+1
-2
@@ -120,7 +120,7 @@ import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import RevenueInputField from '@/components/ui/dashboard/RevenueInputField.vue'
|
||||
import WithdrawFeeBreakdown from '@/components/ui/dashboard/WithdrawFeeBreakdown.vue'
|
||||
import { getAuthUrl, removeAuthProvider, useAuth } from '@/composables/auth.js'
|
||||
import { getAuthUrl, removeAuthProvider, useAuth } from '@/composables/auth.ts'
|
||||
import { useWithdrawContext } from '@/providers/creator-withdraw.ts'
|
||||
|
||||
const { withdrawData, maxWithdrawAmount, availableMethods, calculateFees, saveStateToStorage } =
|
||||
@@ -193,7 +193,6 @@ async function saveVenmoHandle() {
|
||||
},
|
||||
})
|
||||
|
||||
// @ts-expect-error auth.js is not typed
|
||||
await useAuth(auth.value.token)
|
||||
|
||||
initialVenmoHandle.value = venmoHandle.value.trim()
|
||||
|
||||
+1
-1
@@ -358,8 +358,8 @@ import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import RevenueInputField from '@/components/ui/dashboard/RevenueInputField.vue'
|
||||
import WithdrawFeeBreakdown from '@/components/ui/dashboard/WithdrawFeeBreakdown.vue'
|
||||
import { useAuth } from '@/composables/auth.js'
|
||||
import { useWithdrawContext } from '@/providers/creator-withdraw.ts'
|
||||
import { useAuth } from '~/composables/auth.ts'
|
||||
|
||||
const debug = useDebugLogger('TremendousDetailsStage')
|
||||
const { withdrawData, maxWithdrawAmount, availableMethods, paymentOptions, calculateFees } =
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<ButtonStyled circular>
|
||||
<button :class="!collapsed && '[&>svg]:rotate-180'" @click="$emit('toggleCollapsed')">
|
||||
<button :class="{ '[&>svg]:rotate-180': !collapsed }" @click="$emit('toggleCollapsed')">
|
||||
<DropdownIcon class="duration-250 transition-transform ease-in-out" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -190,7 +190,6 @@ import {
|
||||
LinkIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
|
||||
import { type OverflowMenuOption, useFormatDateTime } from '@modrinth/ui'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
@@ -198,6 +197,8 @@ import {
|
||||
getProjectTypeIcon,
|
||||
injectNotificationManager,
|
||||
OverflowMenu,
|
||||
type OverflowMenuOption,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
|
||||
@@ -11,11 +11,15 @@ import {
|
||||
CopyIcon,
|
||||
DownloadIcon,
|
||||
EllipsisVerticalIcon,
|
||||
EyeOffIcon,
|
||||
LinkIcon,
|
||||
LoaderCircleIcon,
|
||||
ScaleIcon,
|
||||
ShieldCheckIcon,
|
||||
SpinnerIcon,
|
||||
TimerIcon,
|
||||
TriangleAlertIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { type TechReviewContext, techReviewQuickReplies } from '@modrinth/moderation'
|
||||
import {
|
||||
@@ -23,15 +27,17 @@ import {
|
||||
ButtonStyled,
|
||||
Collapsible,
|
||||
CollapsibleRegion,
|
||||
commonMessages,
|
||||
getProjectTypeIcon,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
NavTabs,
|
||||
OverflowMenu,
|
||||
type OverflowMenuOption,
|
||||
useFormatBytes,
|
||||
useFormatDateTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { NavTabs } from '@modrinth/ui'
|
||||
import {
|
||||
capitalizeString,
|
||||
formatProjectType,
|
||||
@@ -47,6 +53,7 @@ import ThreadView from '~/components/ui/thread/ThreadView.vue'
|
||||
|
||||
const auth = await useAuth()
|
||||
const featureFlags = useFeatureFlags()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const formatDateTimeUtc = useFormatDateTime({
|
||||
year: 'numeric',
|
||||
@@ -96,6 +103,16 @@ const emit = defineEmits<{
|
||||
showMaliciousSummary: [unsafeFiles: UnsafeFile[]]
|
||||
}>()
|
||||
|
||||
const projectStatus = ref<Labrinth.Projects.v2.ProjectStatus>(props.item.project.status)
|
||||
const isProjectApproved = computed(() => {
|
||||
return (
|
||||
projectStatus.value === 'approved' ||
|
||||
projectStatus.value === 'archived' ||
|
||||
projectStatus.value === 'unlisted' ||
|
||||
projectStatus.value === 'private'
|
||||
)
|
||||
})
|
||||
|
||||
const quickActions = computed<OverflowMenuOption[]>(() => {
|
||||
const actions: OverflowMenuOption[] = []
|
||||
|
||||
@@ -141,6 +158,53 @@ const quickActions = computed<OverflowMenuOption[]>(() => {
|
||||
return actions
|
||||
})
|
||||
|
||||
const isLoadingStatusAction = ref(false)
|
||||
const projectStatusActions = computed<OverflowMenuOption[]>(() => [
|
||||
{
|
||||
id: 'approve',
|
||||
color: 'green',
|
||||
action: () => setStatus('approved'),
|
||||
hoverFilled: true,
|
||||
disabled: isProjectApproved.value || isLoadingStatusAction.value,
|
||||
},
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'orange',
|
||||
action: () => setStatus('withheld'),
|
||||
hoverFilled: true,
|
||||
disabled: projectStatus.value === 'withheld' || isLoadingStatusAction.value,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () => setStatus('processing'),
|
||||
hoverFilled: true,
|
||||
disabled: projectStatus.value === 'processing' || isLoadingStatusAction.value,
|
||||
},
|
||||
{
|
||||
id: 'reject',
|
||||
color: 'red',
|
||||
action: () => setStatus('rejected'),
|
||||
hoverFilled: true,
|
||||
disabled: projectStatus.value === 'rejected' || isLoadingStatusAction.value,
|
||||
},
|
||||
])
|
||||
|
||||
async function setStatus(status: Labrinth.Projects.v2.ProjectStatus) {
|
||||
isLoadingStatusAction.value = true
|
||||
try {
|
||||
await client.labrinth.projects_v2.edit(props.item.project.id, { status })
|
||||
|
||||
projectStatus.value = status
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: (err as any)?.data?.description ? (err as any).data.description : String(err),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
isLoadingStatusAction.value = false
|
||||
}
|
||||
|
||||
type Tab = 'Thread' | 'Files' | 'File'
|
||||
const tabs: readonly ('Thread' | 'Files')[] = ['Thread', 'Files']
|
||||
const currentTab = ref<Tab>('Thread')
|
||||
@@ -347,13 +411,6 @@ const severityColor = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const isProjectApproved = computed(() => {
|
||||
const status = props.item.project.status
|
||||
return (
|
||||
status === 'approved' || status === 'archived' || status === 'unlisted' || status === 'private'
|
||||
)
|
||||
})
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
const dates = props.item.reports.map((r) => new Date(r.created))
|
||||
const earliest = new Date(Math.min(...dates.map((d) => d.getTime())))
|
||||
@@ -1117,6 +1174,37 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
<BugIcon /> Fail
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="standard">
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:disabled="isLoadingStatusAction"
|
||||
:options="projectStatusActions"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="isLoadingStatusAction"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
Set Status
|
||||
<template #approve>
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review
|
||||
</template>
|
||||
<template #reject>
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="featureFlags.developerMode" type="outlined">
|
||||
<button @click="emit('showMaliciousSummary', unsafeFiles)">Debug Summary</button>
|
||||
</ButtonStyled>
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ const currentProjectId = computed(() => projectV3.value?.id)
|
||||
const { selectedProjectId, selectedVersionId } = injectServerCompatibilityContext()
|
||||
const { labrinth } = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const auth = (await useAuth()) as { user?: { id: string } }
|
||||
const auth = await useAuth()
|
||||
|
||||
interface VersionInfo {
|
||||
id: string
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="visibleQuickReplies.length > 0">
|
||||
<OverflowMenu :options="visibleQuickReplies">
|
||||
Quick Reply
|
||||
Quick reply
|
||||
<ChevronDownIcon />
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
function normalizeAuthToken(value) {
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const useAuth = async (oldToken = null) => {
|
||||
const auth = useState('auth', () => ({
|
||||
user: null,
|
||||
token: '',
|
||||
headers: {},
|
||||
}))
|
||||
|
||||
if (!auth.value.user || oldToken) {
|
||||
auth.value = await initAuth(oldToken)
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
export const initAuth = async (oldToken = null) => {
|
||||
const auth = {
|
||||
user: null,
|
||||
token: '',
|
||||
}
|
||||
|
||||
if (oldToken === 'none') {
|
||||
return auth
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
const authCookie = useCookie('auth-token', {
|
||||
maxAge: 60 * 60 * 24 * 365 * 10,
|
||||
sameSite: 'lax',
|
||||
secure: config.public.cookieSecure,
|
||||
httpOnly: false,
|
||||
path: '/',
|
||||
})
|
||||
|
||||
if (oldToken) {
|
||||
const normalized = normalizeAuthToken(oldToken)
|
||||
if (normalized) {
|
||||
authCookie.value = normalized
|
||||
}
|
||||
}
|
||||
|
||||
const oauthCode = normalizeAuthToken(route.query.code)
|
||||
if (oauthCode && !route.fullPath.includes('new_account=true')) {
|
||||
authCookie.value = oauthCode
|
||||
}
|
||||
|
||||
if (route.fullPath.includes('new_account=true') && route.path !== '/auth/welcome') {
|
||||
const redirect = route.path.startsWith('/auth/') ? null : route.fullPath
|
||||
|
||||
await navigateTo(
|
||||
`/auth/welcome?authToken=${oauthCode}${
|
||||
redirect ? `&redirect=${encodeURIComponent(redirect)}` : ''
|
||||
}`,
|
||||
)
|
||||
}
|
||||
|
||||
const tokenStr = normalizeAuthToken(authCookie.value)
|
||||
|
||||
if (authCookie.value != null && tokenStr === '') {
|
||||
authCookie.value = null
|
||||
} else if (tokenStr) {
|
||||
auth.token = tokenStr
|
||||
|
||||
if (!auth.token.startsWith('mra_')) {
|
||||
return auth
|
||||
}
|
||||
|
||||
try {
|
||||
auth.user = await useBaseFetch(
|
||||
'user',
|
||||
{
|
||||
apiVersion: 3,
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
},
|
||||
true,
|
||||
)
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
if (!auth.user && auth.token && typeof auth.token === 'string') {
|
||||
try {
|
||||
const session = await useBaseFetch(
|
||||
'session/refresh',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
auth.token = normalizeAuthToken(session.session)
|
||||
if (auth.token) {
|
||||
authCookie.value = auth.token
|
||||
auth.user = await useBaseFetch(
|
||||
'user',
|
||||
{
|
||||
apiVersion: 3,
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
},
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
authCookie.value = null
|
||||
auth.token = ''
|
||||
}
|
||||
} catch {
|
||||
authCookie.value = null
|
||||
}
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
export const getSignInRedirectPath = (route) => {
|
||||
const fullPath = route.fullPath
|
||||
if (fullPath === '/auth' || fullPath.startsWith('/auth/')) {
|
||||
return '/dashboard'
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
export const getSignInRouteObj = (route, redirectOverride) => ({
|
||||
path: '/auth/sign-in',
|
||||
query: {
|
||||
redirect: redirectOverride ?? getSignInRedirectPath(route),
|
||||
},
|
||||
})
|
||||
|
||||
export const getAuthUrl = (provider, redirect = '/dashboard') => {
|
||||
const config = useRuntimeConfig()
|
||||
const route = useNativeRoute()
|
||||
|
||||
const fullURL = route.query.launcher
|
||||
? getLauncherRedirectUrl(route)
|
||||
: `${config.public.siteUrl}/auth/sign-in?redirect=${encodeURIComponent(redirect)}`
|
||||
|
||||
return `${config.public.apiBaseUrl}auth/init?provider=${provider}&url=${encodeURIComponent(fullURL)}`
|
||||
}
|
||||
|
||||
export const removeAuthProvider = async (provider) => {
|
||||
startLoading()
|
||||
|
||||
const auth = await useAuth()
|
||||
|
||||
await useBaseFetch('auth/provider', {
|
||||
method: 'DELETE',
|
||||
body: {
|
||||
provider,
|
||||
},
|
||||
})
|
||||
|
||||
await useAuth(auth.value.token)
|
||||
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
export const getLauncherRedirectUrl = (route) => {
|
||||
const usesLocalhostRedirectionScheme =
|
||||
['4', '6'].includes(route.query.ipver) && Number(route.query.port) < 65536
|
||||
|
||||
return usesLocalhostRedirectionScheme
|
||||
? `http://${route.query.ipver === '4' ? '127.0.0.1' : '[::1]'}:${route.query.port}`
|
||||
: `https://launcher-files.modrinth.com`
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { LocationQueryValue, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import type { CookieOptions } from '#app'
|
||||
|
||||
type AuthState = {
|
||||
user: Labrinth.Users.v2.User | null
|
||||
token: string
|
||||
}
|
||||
|
||||
type QueryValue = LocationQueryValue | LocationQueryValue[] | undefined
|
||||
type FullPathRoute = Pick<RouteLocationNormalizedLoaded, 'fullPath'>
|
||||
type LauncherRoute = Pick<RouteLocationNormalizedLoaded, 'query'>
|
||||
|
||||
export const LAST_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY = 'auth-last-sign-in-oauth-provider'
|
||||
export const PENDING_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY = 'auth-pending-sign-in-oauth-provider'
|
||||
|
||||
const AUTH_COOKIE_OPTIONS = {
|
||||
maxAge: 60 * 60 * 24 * 365 * 10,
|
||||
sameSite: 'lax',
|
||||
httpOnly: false,
|
||||
path: '/',
|
||||
} satisfies CookieOptions<string | null>
|
||||
|
||||
const normalizeAuthToken = (value: unknown) => {
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const getQueryString = (value: QueryValue) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value[0] ?? null
|
||||
}
|
||||
return value ?? null
|
||||
}
|
||||
|
||||
export const useAuth = async (oldToken: string | null | undefined = null) => {
|
||||
const auth = useState<AuthState>('auth', () => ({
|
||||
user: null,
|
||||
token: '',
|
||||
}))
|
||||
|
||||
if (!auth.value.user || oldToken) {
|
||||
auth.value = await initAuth(oldToken)
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
export const initAuth = async (oldToken: string | null | undefined = null) => {
|
||||
const auth: AuthState = {
|
||||
user: null,
|
||||
token: '',
|
||||
}
|
||||
|
||||
if (oldToken === 'none') {
|
||||
return auth
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
const authCookie = useCookie<string | null>('auth-token', {
|
||||
...AUTH_COOKIE_OPTIONS,
|
||||
secure: config.public.cookieSecure,
|
||||
})
|
||||
|
||||
if (oldToken) {
|
||||
const normalized = normalizeAuthToken(oldToken)
|
||||
if (normalized) {
|
||||
authCookie.value = normalized
|
||||
}
|
||||
}
|
||||
|
||||
const oauthCode = normalizeAuthToken(route.query.code)
|
||||
if (oauthCode && !route.fullPath.includes('new_account=true')) {
|
||||
authCookie.value = oauthCode
|
||||
}
|
||||
|
||||
if (route.fullPath.includes('new_account=true') && route.path !== '/auth/welcome') {
|
||||
const redirect = route.path.startsWith('/auth/') ? null : route.fullPath
|
||||
|
||||
await navigateTo(
|
||||
`/auth/welcome?authToken=${oauthCode}${
|
||||
redirect ? `&redirect=${encodeURIComponent(redirect)}` : ''
|
||||
}`,
|
||||
)
|
||||
}
|
||||
|
||||
const tokenStr = normalizeAuthToken(authCookie.value)
|
||||
|
||||
if (authCookie.value != null && tokenStr === '') {
|
||||
authCookie.value = null
|
||||
} else if (tokenStr) {
|
||||
auth.token = tokenStr
|
||||
|
||||
if (!auth.token.startsWith('mra_')) {
|
||||
return auth
|
||||
}
|
||||
|
||||
try {
|
||||
auth.user = (await useBaseFetch(
|
||||
'user',
|
||||
{
|
||||
apiVersion: 3,
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
},
|
||||
true,
|
||||
)) as Labrinth.Users.v2.User
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
if (!auth.user && auth.token) {
|
||||
try {
|
||||
const session = (await useBaseFetch(
|
||||
'session/refresh',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
},
|
||||
true,
|
||||
)) as { session: unknown }
|
||||
|
||||
auth.token = normalizeAuthToken(session.session)
|
||||
if (auth.token) {
|
||||
authCookie.value = auth.token
|
||||
auth.user = (await useBaseFetch(
|
||||
'user',
|
||||
{
|
||||
apiVersion: 3,
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
},
|
||||
true,
|
||||
)) as Labrinth.Users.v2.User
|
||||
} else {
|
||||
authCookie.value = null
|
||||
auth.token = ''
|
||||
}
|
||||
} catch {
|
||||
authCookie.value = null
|
||||
}
|
||||
}
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
export const getSignInRedirectPath = (route: FullPathRoute) => {
|
||||
const fullPath = route.fullPath
|
||||
if (fullPath === '/auth' || fullPath.startsWith('/auth/')) {
|
||||
return '/dashboard'
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
export const getSignInRouteObj = (route: FullPathRoute, redirectOverride?: string | null) => ({
|
||||
path: '/auth/sign-in',
|
||||
query: {
|
||||
redirect: redirectOverride ?? getSignInRedirectPath(route),
|
||||
},
|
||||
})
|
||||
|
||||
export const getAuthUrl = (provider: string, redirect = '/dashboard') => {
|
||||
const config = useRuntimeConfig()
|
||||
const route = useNativeRoute()
|
||||
const launcher = getQueryString(route.query.launcher)
|
||||
|
||||
const fullURL = launcher
|
||||
? (() => {
|
||||
const callbackUrl = new URL('/auth/sign-in', config.public.siteUrl)
|
||||
callbackUrl.searchParams.set('launcher', launcher)
|
||||
|
||||
const ipver = getQueryString(route.query.ipver)
|
||||
const port = getQueryString(route.query.port)
|
||||
|
||||
if (ipver) {
|
||||
callbackUrl.searchParams.set('ipver', ipver)
|
||||
}
|
||||
|
||||
if (port) {
|
||||
callbackUrl.searchParams.set('port', port)
|
||||
}
|
||||
|
||||
return callbackUrl.toString()
|
||||
})()
|
||||
: `${config.public.siteUrl}/auth/sign-in?redirect=${encodeURIComponent(redirect)}`
|
||||
|
||||
return `${config.public.apiBaseUrl}auth/init?provider=${provider}&url=${encodeURIComponent(fullURL)}`
|
||||
}
|
||||
|
||||
export const promotePendingSignInOAuthProvider = () => {
|
||||
if (!import.meta.client) return
|
||||
const pending = useStorage<string | null>(
|
||||
PENDING_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY,
|
||||
null,
|
||||
undefined,
|
||||
{ initOnMounted: true },
|
||||
)
|
||||
if (!pending.value) return
|
||||
const last = useStorage<string | null>(LAST_SIGN_IN_OAUTH_PROVIDER_STORAGE_KEY, null, undefined, {
|
||||
initOnMounted: true,
|
||||
})
|
||||
last.value = pending.value
|
||||
pending.value = null
|
||||
}
|
||||
|
||||
export const removeAuthProvider = async (provider: string) => {
|
||||
startLoading()
|
||||
|
||||
const auth = await useAuth()
|
||||
|
||||
await useBaseFetch('auth/provider', {
|
||||
method: 'DELETE',
|
||||
body: {
|
||||
provider,
|
||||
},
|
||||
})
|
||||
|
||||
await useAuth(auth.value.token)
|
||||
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
export const getLauncherRedirectUrl = (route: LauncherRoute) => {
|
||||
const ipver = getQueryString(route.query.ipver)
|
||||
const port = Number(getQueryString(route.query.port))
|
||||
const usesLocalhostRedirectionScheme = ['4', '6'].includes(ipver ?? '') && port < 65536
|
||||
|
||||
return usesLocalhostRedirectionScheme
|
||||
? `http://${ipver === '4' ? '127.0.0.1' : '[::1]'}:${port}`
|
||||
: 'https://launcher-files.modrinth.com'
|
||||
}
|
||||
@@ -48,7 +48,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
useV1ContentTabAPI: true,
|
||||
labrinthApiCanary: false,
|
||||
dismissedExternalProjectsInfo: false,
|
||||
modpackPermissionsPage: false,
|
||||
showAllBanners: false,
|
||||
alwaysIgnoreErrorBanner: false,
|
||||
showViewProdRouteBanner: false,
|
||||
@@ -56,6 +55,8 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
showModeratorPrivateMessageHighlight: true,
|
||||
archonApiStaging: false,
|
||||
showHostingAccessInstanceAuditLog: false,
|
||||
versionDevInfoCollapsed: true,
|
||||
alwaysShowVersionDevInfo: false,
|
||||
} as const)
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
|
||||
@@ -8,4 +8,11 @@ export const versionQueryOptions = {
|
||||
queryFn: () => client.labrinth.versions_v3.getVersion(versionId),
|
||||
staleTime: STALE_TIME,
|
||||
}),
|
||||
|
||||
fromProject: (projectId: string, versionIdOrNumber: string, client: AbstractModrinthClient) => ({
|
||||
queryKey: ['project', projectId, 'version', 'v3', versionIdOrNumber] as const,
|
||||
queryFn: () =>
|
||||
client.labrinth.versions_v3.getVersionFromIdOrNumber(projectId, versionIdOrNumber),
|
||||
staleTime: STALE_TIME,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -132,23 +132,39 @@ export function createLoaderParsers(
|
||||
),
|
||||
}
|
||||
},
|
||||
// Fabric
|
||||
// Fabric (or Babric for mc version beta 1.7.3)
|
||||
'fabric.mod.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
const detectedGameVersions = metadata.depends
|
||||
const mcDependency = metadata.depends?.minecraft
|
||||
const mcDependencies = Array.isArray(mcDependency) ? mcDependency : [mcDependency]
|
||||
|
||||
let detectedGameVersions = metadata.depends
|
||||
? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions)
|
||||
: []
|
||||
const loaders: string[] = []
|
||||
|
||||
// Detect Beta 1.7.3 -> Babric
|
||||
const hasBabricVersion = mcDependencies.some(
|
||||
(version: string | undefined) => version?.includes('1.0.0-beta.7.3'), // this is fabric's normalized mc version format
|
||||
)
|
||||
|
||||
// Detect 1.3-1.13 -> legacy-fabric
|
||||
const hasLegacyVersions = detectedGameVersions.some((version) => {
|
||||
const match = version.match(/^1\.(\d+)/)
|
||||
return match && parseInt(match[1]) >= 3 && parseInt(match[1]) <= 13
|
||||
})
|
||||
|
||||
if (hasLegacyVersions) loaders.push('legacy-fabric')
|
||||
else loaders.push('fabric')
|
||||
if (hasBabricVersion) {
|
||||
loaders.push('babric')
|
||||
detectedGameVersions = gameVersions
|
||||
.filter((version) => version.version === 'b1.7.3')
|
||||
.map((version) => version.version)
|
||||
} else if (hasLegacyVersions) {
|
||||
loaders.push('legacy-fabric')
|
||||
} else {
|
||||
loaders.push('fabric')
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
|
||||
@@ -50,15 +50,15 @@ export function getGameVersionsMatchingMavenRange(
|
||||
ranges.push(range)
|
||||
}
|
||||
|
||||
const LESS_THAN_EQUAL = /^\(,(.*)]$/
|
||||
const LESS_THAN = /^\(,(.*)\)$/
|
||||
const EQUAL = /^\[(.*)]$/
|
||||
const GREATER_THAN_EQUAL = /^\[(.*),\)$/
|
||||
const GREATER_THAN = /^\((.*),\)$/
|
||||
const BETWEEN = /^\((.*),(.*)\)$/
|
||||
const BETWEEN_EQUAL = /^\[(.*),(.*)]$/
|
||||
const BETWEEN_LESS_THAN_EQUAL = /^\((.*),(.*)]$/
|
||||
const BETWEEN_GREATER_THAN_EQUAL = /^\[(.*),(.*)\)$/
|
||||
const LESS_THAN_EQUAL = /^\(,([^,]*)]$/
|
||||
const LESS_THAN = /^\(,([^,]*)\)$/
|
||||
const EQUAL = /^\[([^,]*)]$/
|
||||
const GREATER_THAN_EQUAL = /^\[([^,]*),\)$/
|
||||
const GREATER_THAN = /^\(([^,]*),\)$/
|
||||
const BETWEEN = /^\(([^,]*),([^,]*)\)$/
|
||||
const BETWEEN_EQUAL = /^\[([^,]*),([^,]*)]$/
|
||||
const BETWEEN_LESS_THAN_EQUAL = /^\(([^,]*),([^,]*)]$/
|
||||
const BETWEEN_GREATER_THAN_EQUAL = /^\[([^,]*),([^,]*)\)$/
|
||||
|
||||
const semverRanges = []
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
function ensurePasskeySupported() {
|
||||
const supported =
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window.PublicKeyCredential !== 'undefined' &&
|
||||
typeof navigator !== 'undefined' &&
|
||||
!!navigator.credentials
|
||||
if (!supported) {
|
||||
throw new Error('Passkeys are not supported by this browser.')
|
||||
}
|
||||
}
|
||||
|
||||
function base64urlToBuffer(base64url: string) {
|
||||
return Uint8Array.from(atob(base64url.replace(/-/g, '+').replace(/_/g, '/')), (char) =>
|
||||
char.charCodeAt(0),
|
||||
)
|
||||
}
|
||||
|
||||
function bufferToBase64url(buffer: ArrayBuffer) {
|
||||
const bytes = new Uint8Array(buffer)
|
||||
let str = ''
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
str += String.fromCharCode(bytes[i])
|
||||
}
|
||||
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a passkey credential using the browser's WebAuthn API.
|
||||
*
|
||||
* @param options The public key options for creating the passkey credential, provided by the server.
|
||||
*/
|
||||
export async function createPasskeyCredential(options: any) {
|
||||
ensurePasskeySupported()
|
||||
|
||||
const publicKey = {
|
||||
...options,
|
||||
challenge: base64urlToBuffer(options.challenge),
|
||||
user: {
|
||||
...options.user,
|
||||
id: base64urlToBuffer(options.user.id),
|
||||
},
|
||||
excludeCredentials: options.excludeCredentials?.map((cred: any) => ({
|
||||
...cred,
|
||||
id: base64urlToBuffer(cred.id),
|
||||
})),
|
||||
}
|
||||
|
||||
const credential = (await navigator.credentials.create({ publicKey })) as PublicKeyCredential
|
||||
const response = credential.response as AuthenticatorAttestationResponse
|
||||
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64url(credential.rawId),
|
||||
type: credential.type,
|
||||
response: {
|
||||
clientDataJSON: bufferToBase64url(response.clientDataJSON),
|
||||
attestationObject: bufferToBase64url(response.attestationObject),
|
||||
},
|
||||
extensions: credential.getClientExtensionResults(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user using a passkey credential.
|
||||
*
|
||||
* @param options The public key options for authenticating the passkey credential, provided by the server.
|
||||
*/
|
||||
export async function getPasskeyCredential(options: any) {
|
||||
ensurePasskeySupported()
|
||||
|
||||
const publicKey = {
|
||||
...options,
|
||||
challenge: base64urlToBuffer(options.challenge),
|
||||
allowCredentials: options.allowCredentials?.map((cred: any) => ({
|
||||
...cred,
|
||||
id: base64urlToBuffer(cred.id),
|
||||
})),
|
||||
}
|
||||
|
||||
const credential = (await navigator.credentials.get({ publicKey })) as PublicKeyCredential
|
||||
const response = credential.response as AuthenticatorAssertionResponse
|
||||
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64url(credential.rawId),
|
||||
type: credential.type,
|
||||
response: {
|
||||
clientDataJSON: bufferToBase64url(response.clientDataJSON),
|
||||
authenticatorData: bufferToBase64url(response.authenticatorData),
|
||||
signature: bufferToBase64url(response.signature),
|
||||
userHandle: response.userHandle ? bufferToBase64url(response.userHandle) : null,
|
||||
},
|
||||
extensions: credential.getClientExtensionResults(),
|
||||
}
|
||||
}
|
||||
@@ -799,7 +799,7 @@ import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.
|
||||
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
||||
import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
|
||||
import ModrinthFooter from '~/components/ui/ModrinthFooter.vue'
|
||||
import { getSignInRouteObj } from '~/composables/auth.js'
|
||||
import { getSignInRouteObj } from '~/composables/auth.ts'
|
||||
import { errors as generatedStateErrors } from '~/generated/state.json'
|
||||
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
||||
import { hasActiveMidas } from '~/utils/user-membership.ts'
|
||||
|
||||
@@ -8,6 +8,108 @@
|
||||
"admin.billing.error.not-found": {
|
||||
"message": "المستخدم غير موجود"
|
||||
},
|
||||
"analytics.action.add": {
|
||||
"message": "إضافة"
|
||||
},
|
||||
"analytics.action.cancel": {
|
||||
"message": "إلغاء"
|
||||
},
|
||||
"analytics.action.refresh": {
|
||||
"message": "تحديث"
|
||||
},
|
||||
"analytics.action.reset": {
|
||||
"message": "إعادة ضبط"
|
||||
},
|
||||
"analytics.breakdown.country": {
|
||||
"message": "الدولة"
|
||||
},
|
||||
"analytics.breakdown.download-reason": {
|
||||
"message": "سبب التنزيل"
|
||||
},
|
||||
"analytics.breakdown.download-source": {
|
||||
"message": "مصدر التنزيل"
|
||||
},
|
||||
"analytics.breakdown.game-version": {
|
||||
"message": "إصدار اللُعبة"
|
||||
},
|
||||
"analytics.breakdown.project": {
|
||||
"message": "المشروع"
|
||||
},
|
||||
"analytics.breakdown.project-status": {
|
||||
"message": "حالة المشروع"
|
||||
},
|
||||
"analytics.breakdown.project-version": {
|
||||
"message": "إصدار المشروع"
|
||||
},
|
||||
"analytics.chart.action.show-all": {
|
||||
"message": "أظهار الكل"
|
||||
},
|
||||
"analytics.chart.axis.playtime-hours": {
|
||||
"message": "{hours} ساعة"
|
||||
},
|
||||
"analytics.chart.controls.display": {
|
||||
"message": "عرض"
|
||||
},
|
||||
"analytics.chart.tooltip.total": {
|
||||
"message": "مجموع"
|
||||
},
|
||||
"analytics.downloads.suffix": {
|
||||
"message": "التنزيلات"
|
||||
},
|
||||
"analytics.filter.game-version-type.all": {
|
||||
"message": "الكل"
|
||||
},
|
||||
"analytics.group-by.week": {
|
||||
"message": "الأسبوع"
|
||||
},
|
||||
"analytics.group-by.year": {
|
||||
"message": "السنة"
|
||||
},
|
||||
"analytics.loading.fetching-results": {
|
||||
"message": "جلب البيانات..."
|
||||
},
|
||||
"analytics.options.loading": {
|
||||
"message": "تحميل..."
|
||||
},
|
||||
"analytics.project-event.project-approved": {
|
||||
"message": "المشروع قُبل"
|
||||
},
|
||||
"analytics.project-event.project-private": {
|
||||
"message": "وضع المشروع خاص"
|
||||
},
|
||||
"analytics.project-event.project-status-changed": {
|
||||
"message": "تم تغيير حالة المشروع"
|
||||
},
|
||||
"analytics.project-event.version-released": {
|
||||
"message": ""
|
||||
},
|
||||
"analytics.project-status.draft": {
|
||||
"message": "مسودة"
|
||||
},
|
||||
"analytics.project-status.private": {
|
||||
"message": "خاص"
|
||||
},
|
||||
"analytics.project-status.rejected": {
|
||||
"message": "رُفض المشروع"
|
||||
},
|
||||
"analytics.project.your": {
|
||||
"message": "مشاريعك"
|
||||
},
|
||||
"analytics.query.label.project": {
|
||||
"message": "المشروع:"
|
||||
},
|
||||
"analytics.stat.downloads": {
|
||||
"message": "التنزيلات"
|
||||
},
|
||||
"analytics.stat.playtime": {
|
||||
"message": "وقت اللعب"
|
||||
},
|
||||
"analytics.stat.playtime-hours": {
|
||||
"message": "{hours} ساعة"
|
||||
},
|
||||
"analytics.table.search.placeholder": {
|
||||
"message": "بحث..."
|
||||
},
|
||||
"app-marketing.download.description": {
|
||||
"message": "تطبيقنا لسطح المكتب متاح على جميع المنصات، اختر الإصدار الذي تريده."
|
||||
},
|
||||
@@ -263,39 +365,21 @@
|
||||
"auth.sign-in.2fa.placeholder": {
|
||||
"message": "أدخل الرمز..."
|
||||
},
|
||||
"auth.sign-in.additional-options": {
|
||||
"message": "<forgot-password-link>هل نسيت كلمة المرور؟</forgot-password-link> • <create-account-link>إنشاء حساب</create-account-link>"
|
||||
},
|
||||
"auth.sign-in.sign-in-with": {
|
||||
"message": "تسجيل الدخول باستخدام"
|
||||
},
|
||||
"auth.sign-in.title": {
|
||||
"message": "تسجيل الدخول"
|
||||
},
|
||||
"auth.sign-in.use-password": {
|
||||
"message": "أو استخدم كلمة المرور"
|
||||
},
|
||||
"auth.sign-up.action.create-account": {
|
||||
"message": "إنشاء حساب"
|
||||
},
|
||||
"auth.sign-up.legal-dislaimer": {
|
||||
"message": "بإنشائك حسابًا، فإنك توافق على <terms-link>شروط مودرنث</terms-link> و<privacy-policy-link>سياسة الخصوصية</privacy-policy-link> الخاصة بها."
|
||||
},
|
||||
"auth.sign-up.notification.password-mismatch.text": {
|
||||
"message": "كلمتا المرور غير متطابقتين!"
|
||||
},
|
||||
"auth.sign-up.sign-in-option.title": {
|
||||
"message": "هل لديك حساب بالفعل؟"
|
||||
},
|
||||
"auth.sign-up.subscribe.label": {
|
||||
"message": "اشترك لتصلك التحديثات حول مودرنث"
|
||||
},
|
||||
"auth.sign-up.title": {
|
||||
"message": "إنشاء حساب"
|
||||
},
|
||||
"auth.sign-up.title.create-account": {
|
||||
"message": "أو أنشئ حسابًا بنفسك"
|
||||
},
|
||||
"auth.sign-up.title.sign-up-with": {
|
||||
"message": "سجّل باستخدام"
|
||||
},
|
||||
@@ -329,21 +413,6 @@
|
||||
"auth.verify-email.title": {
|
||||
"message": "تحقّق من البريد الإلكتروني"
|
||||
},
|
||||
"auth.welcome.checkbox.subscribe": {
|
||||
"message": "اشترك في التحديثات حول مودرنث"
|
||||
},
|
||||
"auth.welcome.description": {
|
||||
"message": "أنت الآن جزء من مجتمع رائع من المبدعين والمستكشفين الذين يقومون بالفعل بإنشاء المودات المذهلة، تثبيتها، والبقاء على اطلاع بآخر التحديثات."
|
||||
},
|
||||
"auth.welcome.label.tos": {
|
||||
"message": "بإنشائك حسابًا، فإنك توافق على <terms-link>شروط مودرنث</terms-link> و<privacy-policy-link>سياسة الخصوصية</privacy-policy-link>."
|
||||
},
|
||||
"auth.welcome.long-title": {
|
||||
"message": "مرحبًا بك في مودرنث!"
|
||||
},
|
||||
"auth.welcome.title": {
|
||||
"message": "مرحبًا"
|
||||
},
|
||||
"collection.button.edit-icon": {
|
||||
"message": "تعديل الأيقونة"
|
||||
},
|
||||
@@ -407,6 +476,9 @@
|
||||
"create-project-version.create-modal.stage.add-files.admonition": {
|
||||
"message": "الملفات التكميلية تستخدم لدعم الموارد مثل كود المصدر، وليس للإصدارات أو الأنواع البديلة."
|
||||
},
|
||||
"create.collection.collection-info": {
|
||||
"message": "سيتم إنشاء مجموعتك الجديدة كمجموعة عامة تحتوي على {count, plural,\n one {# مشروع}\n other {# مشاريع}\n}."
|
||||
},
|
||||
"create.collection.create-collection": {
|
||||
"message": "إنشاء مجموعة"
|
||||
},
|
||||
|
||||
@@ -71,6 +71,87 @@
|
||||
"analytics.chart.controls.annotations": {
|
||||
"message": "Anotace"
|
||||
},
|
||||
"analytics.chart.controls.button": {
|
||||
"message": "Ovládání"
|
||||
},
|
||||
"analytics.chart.events.project-title": {
|
||||
"message": "<project>{projectName}</project>: {title}"
|
||||
},
|
||||
"analytics.chart.tooltip.hide-entry": {
|
||||
"message": "Skrýt {name} v grafu"
|
||||
},
|
||||
"analytics.chart.tooltip.show-entry": {
|
||||
"message": "Zobrazit {name} v grafu"
|
||||
},
|
||||
"analytics.chart.view.area": {
|
||||
"message": "Oblast"
|
||||
},
|
||||
"analytics.download-reason.update": {
|
||||
"message": "Aktualizace"
|
||||
},
|
||||
"analytics.downloads.suffix": {
|
||||
"message": "stažení"
|
||||
},
|
||||
"analytics.empty.no-projects": {
|
||||
"message": "Žádné dostupné projekty"
|
||||
},
|
||||
"analytics.empty.no-projects-for-analytics": {
|
||||
"message": "Žádné dostupné projekty pro analýzu "
|
||||
},
|
||||
"analytics.empty.select-project": {
|
||||
"message": "Vyber alespoň jeden projekt pro zobrazení dat"
|
||||
},
|
||||
"analytics.group-by.1h": {
|
||||
"message": "1h"
|
||||
},
|
||||
"analytics.group-by.6h": {
|
||||
"message": "6h"
|
||||
},
|
||||
"analytics.group-by.date": {
|
||||
"message": "Datum"
|
||||
},
|
||||
"analytics.group-by.day": {
|
||||
"message": "Den"
|
||||
},
|
||||
"analytics.group-by.month": {
|
||||
"message": "Měsíc"
|
||||
},
|
||||
"analytics.group-by.week": {
|
||||
"message": "Týden "
|
||||
},
|
||||
"analytics.group-by.year": {
|
||||
"message": "Rok"
|
||||
},
|
||||
"analytics.options.loading": {
|
||||
"message": "Načítání..."
|
||||
},
|
||||
"analytics.project-status.approved": {
|
||||
"message": "Schválený"
|
||||
},
|
||||
"analytics.project-status.archived": {
|
||||
"message": "Archivovaný "
|
||||
},
|
||||
"analytics.project-status.other": {
|
||||
"message": "Jiné"
|
||||
},
|
||||
"analytics.project-status.private": {
|
||||
"message": "Soukromý "
|
||||
},
|
||||
"analytics.project-status.unlisted": {
|
||||
"message": "Neveřejný "
|
||||
},
|
||||
"analytics.project-status.withheld": {
|
||||
"message": "Pozastavený"
|
||||
},
|
||||
"analytics.project.all": {
|
||||
"message": "Všechny projekty"
|
||||
},
|
||||
"analytics.query.label.project": {
|
||||
"message": "Projekt:"
|
||||
},
|
||||
"analytics.value.other": {
|
||||
"message": "Jiné"
|
||||
},
|
||||
"app-marketing.download.description": {
|
||||
"message": "Naše aplikace je dostupná pro všechny počítačové platformy, vyberte si požadovanou verzi."
|
||||
},
|
||||
@@ -326,39 +407,21 @@
|
||||
"auth.sign-in.2fa.placeholder": {
|
||||
"message": "Zadejte kód..."
|
||||
},
|
||||
"auth.sign-in.additional-options": {
|
||||
"message": "<forgot-password-link>Zapomněli jste heslo?</forgot-password-link> • <create-account-link>Vytvořte si účet</create-account-link>"
|
||||
},
|
||||
"auth.sign-in.sign-in-with": {
|
||||
"message": "Přihlášte se pomocí"
|
||||
},
|
||||
"auth.sign-in.title": {
|
||||
"message": "Přihlásit se"
|
||||
},
|
||||
"auth.sign-in.use-password": {
|
||||
"message": "Nebo použijte heslo"
|
||||
},
|
||||
"auth.sign-up.action.create-account": {
|
||||
"message": "Vytvořte účet"
|
||||
},
|
||||
"auth.sign-up.legal-dislaimer": {
|
||||
"message": "Vytvořením účtu souhlasíte s <terms-link>Podmínkami</terms-link> a <privacy-policy-link>Zásadami ochrany osobních údajů</privacy-policy-link> Modrinthu."
|
||||
},
|
||||
"auth.sign-up.notification.password-mismatch.text": {
|
||||
"message": "Hesla se neshodují!"
|
||||
},
|
||||
"auth.sign-up.sign-in-option.title": {
|
||||
"message": "Již máte účet?"
|
||||
},
|
||||
"auth.sign-up.subscribe.label": {
|
||||
"message": "Přihlásit se k odběru informací o Modrinthu"
|
||||
},
|
||||
"auth.sign-up.title": {
|
||||
"message": "Registrace"
|
||||
},
|
||||
"auth.sign-up.title.create-account": {
|
||||
"message": "Nebo si vytvořte účet"
|
||||
},
|
||||
"auth.sign-up.title.sign-up-with": {
|
||||
"message": "Registrujte se pomocí"
|
||||
},
|
||||
@@ -392,21 +455,6 @@
|
||||
"auth.verify-email.title": {
|
||||
"message": "Ověřit e-mail"
|
||||
},
|
||||
"auth.welcome.checkbox.subscribe": {
|
||||
"message": "Přihlásit se k odběru informací o Modrinthu"
|
||||
},
|
||||
"auth.welcome.description": {
|
||||
"message": "Nyní jste součástí úžasné komunity tvůrců a průzkumníků, kteří již vytvářejí, stahují a aktualizují úžasné módy."
|
||||
},
|
||||
"auth.welcome.label.tos": {
|
||||
"message": "Vytvořením účtu souhlasíte s <terms-link>Podmínkami</terms-link> a <privacy-policy-link>Zásadami ochrany osobních údajů</privacy-policy-link> Modrinthu."
|
||||
},
|
||||
"auth.welcome.long-title": {
|
||||
"message": "Vítejte v Modrinthu!"
|
||||
},
|
||||
"auth.welcome.title": {
|
||||
"message": "Vítejte"
|
||||
},
|
||||
"collection.button.edit-icon": {
|
||||
"message": "Upravit ikonu"
|
||||
},
|
||||
@@ -467,6 +515,12 @@
|
||||
"conversation-thread.action.add-private-note": {
|
||||
"message": "Přidat soukromou poznámku"
|
||||
},
|
||||
"conversation-thread.action.approve": {
|
||||
"message": "Schválit"
|
||||
},
|
||||
"conversation-thread.action.approve-with-reply": {
|
||||
"message": "Schválit s odpovědí"
|
||||
},
|
||||
"conversation-thread.action.close-thread": {
|
||||
"message": "Uzavřít vlákno"
|
||||
},
|
||||
@@ -488,6 +542,12 @@
|
||||
"conversation-thread.action.reply-to-thread": {
|
||||
"message": "Odpovědět vláknu"
|
||||
},
|
||||
"conversation-thread.action.resubmit-for-review": {
|
||||
"message": "Znovu odeslat ke kontrole"
|
||||
},
|
||||
"conversation-thread.action.resubmit-for-review-with-reply": {
|
||||
"message": "Znovu odeslat ke kontrole s odpovědí "
|
||||
},
|
||||
"conversation-thread.action.send": {
|
||||
"message": "Odeslat"
|
||||
},
|
||||
@@ -497,6 +557,12 @@
|
||||
"conversation-thread.action.send-to-review-with-reply": {
|
||||
"message": "Odeslat ke kontrole s odpovědí"
|
||||
},
|
||||
"conversation-thread.action.withhold": {
|
||||
"message": "Pozastavit "
|
||||
},
|
||||
"conversation-thread.action.withhold-with-reply": {
|
||||
"message": "Pozastavit s odpovědí"
|
||||
},
|
||||
"conversation-thread.reply-editor.placeholder.reply": {
|
||||
"message": "Odpovědět vláknu..."
|
||||
},
|
||||
@@ -509,6 +575,9 @@
|
||||
"create-project-version.create-modal.stage.add-files.admonition": {
|
||||
"message": "Doplňkové soubory slouží jako podpůrné materiály (např. zdrojový kód), ne jako alternativní verze či varianty."
|
||||
},
|
||||
"create.collection.collection-info": {
|
||||
"message": "Tvá nová kolekce bude vytvořena jako veřejná a bude obsahovat {count, plural, =0 {žádné projekty} one {# projekt} few {# projekty} other {# projektů}}."
|
||||
},
|
||||
"create.collection.create-collection": {
|
||||
"message": "Vytvořit kolekci"
|
||||
},
|
||||
@@ -683,6 +752,9 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Nedávno aktualizováno"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Stáhnout {formType}"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.title": {
|
||||
"message": "Hotovo! 🎉"
|
||||
},
|
||||
@@ -717,7 +789,7 @@
|
||||
"message": "Vyhladaj štát..."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.method-selection.region": {
|
||||
"message": "Región"
|
||||
"message": "Oblast"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.account-owner": {
|
||||
"message": "Majitel účtu"
|
||||
@@ -737,6 +809,9 @@
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.document-number-tax-id-placeholder": {
|
||||
"message": "Zadejte DIČ"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.network": {
|
||||
"message": "Síť"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.paypal-details.account": {
|
||||
"message": "Účet"
|
||||
},
|
||||
@@ -756,7 +831,7 @@
|
||||
"message": "Přihlásit přes PayPal"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.tax-form": {
|
||||
"message": ""
|
||||
"message": "Daňový formulář"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.enter-denomination-placeholder": {
|
||||
"message": "Zadejte množství"
|
||||
@@ -779,6 +854,9 @@
|
||||
"dashboard.creator-withdraw-modal.withdraw-limit-used": {
|
||||
"message": "Vyčerpali jste svůj limit pro výběr <b>{withdrawLimit}</b>. Pro vyšší výběr musíte vyplnit daňový formulář."
|
||||
},
|
||||
"dashboard.discord-roles.role.creator": {
|
||||
"message": "Tvůrce"
|
||||
},
|
||||
"dashboard.organizations.button.create": {
|
||||
"message": "Vytvořit organizaci"
|
||||
},
|
||||
@@ -788,12 +866,18 @@
|
||||
"dashboard.organizations.error.fetch": {
|
||||
"message": "Nepodařilo se načíst organizace"
|
||||
},
|
||||
"dashboard.organizations.title": {
|
||||
"message": "Organizace"
|
||||
},
|
||||
"dashboard.overview.notifications.button.mark-all-as-read": {
|
||||
"message": "Označit vše jako přečtené"
|
||||
},
|
||||
"dashboard.overview.notifications.button.view-history": {
|
||||
"message": "Ukázat historii"
|
||||
},
|
||||
"dashboard.overview.notifications.empty.no-unread": {
|
||||
"message": "Nemáš žádná nepřečtená oznámení."
|
||||
},
|
||||
"dashboard.overview.notifications.history.label": {
|
||||
"message": "Historie"
|
||||
},
|
||||
@@ -806,6 +890,9 @@
|
||||
"dashboard.projects.head-title": {
|
||||
"message": "Projekty"
|
||||
},
|
||||
"dashboard.projects.links.button.edit": {
|
||||
"message": "Upravit odkazy"
|
||||
},
|
||||
"dashboard.projects.links.discord-invite.label": {
|
||||
"message": "Discord pozvánka"
|
||||
},
|
||||
@@ -824,6 +911,9 @@
|
||||
"dashboard.projects.table.status": {
|
||||
"message": "Stav"
|
||||
},
|
||||
"dashboard.report.title": {
|
||||
"message": "Nahlásit {id}"
|
||||
},
|
||||
"dashboard.revenue.available-now": {
|
||||
"message": "K dispozici nyní"
|
||||
},
|
||||
@@ -1268,6 +1358,9 @@
|
||||
"profile.meta.description-with-bio": {
|
||||
"message": "{bio} - Stáhněte si projekty od {username} na Modrinthu"
|
||||
},
|
||||
"profile.stats.projects-followers": {
|
||||
"message": "{count, plural, one {<stat>{count}</stat> sledující projektu} few {<stat>{count}</stat> sledující projektu} other {<stat>{count}</stat> sledujících projektu}}"
|
||||
},
|
||||
"profile.user-id": {
|
||||
"message": "ID uživatele: {id}"
|
||||
},
|
||||
|
||||
@@ -263,39 +263,21 @@
|
||||
"auth.sign-in.2fa.placeholder": {
|
||||
"message": "Indtast kode..."
|
||||
},
|
||||
"auth.sign-in.additional-options": {
|
||||
"message": "<forgot-password-link>Glemt adgangskode?</forgot-password-link> • <create-account-link>Opret en konto</create-account-link>"
|
||||
},
|
||||
"auth.sign-in.sign-in-with": {
|
||||
"message": "Log ind med"
|
||||
},
|
||||
"auth.sign-in.title": {
|
||||
"message": "Log ind"
|
||||
},
|
||||
"auth.sign-in.use-password": {
|
||||
"message": "Eller brug en adgangskode"
|
||||
},
|
||||
"auth.sign-up.action.create-account": {
|
||||
"message": "Opret konto"
|
||||
},
|
||||
"auth.sign-up.legal-dislaimer": {
|
||||
"message": "Ved at oprette en konto, accepterer du Modrinth's <terms-link>Vilkår</terms-link> og <privacy-policy-link>Privatlivspolitik</privacy-policy-link>."
|
||||
},
|
||||
"auth.sign-up.notification.password-mismatch.text": {
|
||||
"message": "Adgangskoderne er ikke ens!"
|
||||
},
|
||||
"auth.sign-up.sign-in-option.title": {
|
||||
"message": "Har du allerede en konto?"
|
||||
},
|
||||
"auth.sign-up.subscribe.label": {
|
||||
"message": "Abonner på opdateringer om Modrinth"
|
||||
},
|
||||
"auth.sign-up.title": {
|
||||
"message": "Tilmeld"
|
||||
},
|
||||
"auth.sign-up.title.create-account": {
|
||||
"message": "Eller opret en konto selv"
|
||||
},
|
||||
"auth.sign-up.title.sign-up-with": {
|
||||
"message": "Tilmeld dig med"
|
||||
},
|
||||
@@ -329,21 +311,6 @@
|
||||
"auth.verify-email.title": {
|
||||
"message": "Bekræft E-mail"
|
||||
},
|
||||
"auth.welcome.checkbox.subscribe": {
|
||||
"message": "Abonner på opdateringer om Modrinth"
|
||||
},
|
||||
"auth.welcome.description": {
|
||||
"message": "Du er nu en del af det awesome fællesskab og skabere og udforskere der allerede bygger, downloader, og forbliver opdateret med fantastiske mods."
|
||||
},
|
||||
"auth.welcome.label.tos": {
|
||||
"message": "Ved at oprette en konto, accepterer du Modrinth's <terms-link>Vilkår</terms-link> og <privacy-policy-link>Privatlivspolitik</privacy-policy-link>."
|
||||
},
|
||||
"auth.welcome.long-title": {
|
||||
"message": "Velkommen til Modrinth!"
|
||||
},
|
||||
"auth.welcome.title": {
|
||||
"message": "Velkommen"
|
||||
},
|
||||
"collection.button.edit-icon": {
|
||||
"message": "Rediger ikon"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user