mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6257940747 | ||
|
|
47c60788ff | ||
|
|
d1e8a9c574 | ||
|
|
7bf85239de | ||
|
|
6380dd16d5 | ||
|
|
2376d571bb | ||
|
|
e7926083fb | ||
|
|
a686a93858 | ||
|
|
2876890fe5 | ||
|
|
4c869d2a37 | ||
|
|
525f60c389 | ||
|
|
042a27a17c | ||
|
|
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 | ||
|
|
f48a2f64db | ||
|
|
fe80ac10fc | ||
|
|
cf0d260802 | ||
|
|
503d34ee0f | ||
|
|
bfbe66f73b | ||
|
|
64e17c7c1b | ||
|
|
c082594825 | ||
|
|
e5831d38eb | ||
|
|
1cabfe3e85 | ||
|
|
36423eb5b5 | ||
|
|
7d15fd3ac0 | ||
|
|
c1780eef7d | ||
|
|
d2a66bb2b0 | ||
|
|
98b1730e19 | ||
|
|
180cef6eaa | ||
|
|
b828fa17de | ||
|
|
72a4e86c26 | ||
|
|
93f8da1666 | ||
|
|
f474940321 | ||
|
|
83b0586fd2 | ||
|
|
543d25e2d6 | ||
|
|
bc5a761312 | ||
|
|
3258d7dbdf | ||
|
|
b5d1aeda85 |
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ node_modules
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/i18n-ally-custom-framework.yml
|
||||
|
||||
# IDE - IntelliJ
|
||||
.idea/*
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode", "Vue.volar", "rust-lang.rust-analyzer"]
|
||||
"recommendations": ["esbenp.prettier-vscode", "Vue.volar", "rust-lang.rust-analyzer", "lokalise.i18n-ally"]
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
languageIds:
|
||||
- vue
|
||||
- typescript
|
||||
- javascript
|
||||
- typescriptreact
|
||||
|
||||
usageMatchRegex:
|
||||
- id:\s*['"]({key})['"]
|
||||
|
||||
monopoly: true
|
||||
Vendored
+11
-1
@@ -31,5 +31,15 @@
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
},
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"scss.lint.unknownAtRules": "ignore"
|
||||
"scss.lint.unknownAtRules": "ignore",
|
||||
"i18n-ally.localesPaths": [
|
||||
"packages/ui/src/locales",
|
||||
"apps/frontend/src/locales",
|
||||
"packages/moderation/src/locales"
|
||||
],
|
||||
"i18n-ally.pathMatcher": "{locale}/index.{ext}",
|
||||
"i18n-ally.keystyle": "flat",
|
||||
"i18n-ally.sourceLanguage": "en-US",
|
||||
"i18n-ally.namespace": false,
|
||||
"i18n-ally.includeSubfolders": true
|
||||
}
|
||||
|
||||
Generated
+45
-1
@@ -4817,7 +4817,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 +5277,7 @@ dependencies = [
|
||||
"quick-xml 0.38.3",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rdkafka",
|
||||
"redis",
|
||||
"regex",
|
||||
"reqwest 0.12.24",
|
||||
@@ -5489,6 +5490,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"
|
||||
@@ -7967,6 +7980,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"
|
||||
|
||||
@@ -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 }
|
||||
@@ -269,6 +270,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"
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.0",
|
||||
"vue-router": "^4.6.0",
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.1.1",
|
||||
|
||||
@@ -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:', {
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
import { type MinecraftAuthError, minecraftAuthErrors } from './minecraft-auth-errors'
|
||||
import { findMinecraftAuthError, type MinecraftAuthError } from './minecraft-auth-errors'
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const rawError = ref<string>('')
|
||||
@@ -26,7 +26,7 @@ const loadingSignIn = ref(false)
|
||||
function show(errorVal: { message?: string }) {
|
||||
rawError.value = errorVal?.message ?? String(errorVal)
|
||||
|
||||
matchedError.value = minecraftAuthErrors.find((e) => rawError.value.includes(e.errorCode)) ?? null
|
||||
matchedError.value = findMinecraftAuthError(rawError.value)
|
||||
|
||||
debugCollapsed.value = true
|
||||
hide_ads_window()
|
||||
|
||||
+111
-1
@@ -1,10 +1,93 @@
|
||||
export interface MinecraftAuthError {
|
||||
errorCode: string
|
||||
errorCode?: string
|
||||
errorMatchers?: string[]
|
||||
matches?: (message: string) => boolean
|
||||
whatHappened: string
|
||||
stepsToFix: string[]
|
||||
}
|
||||
|
||||
export const minecraftAuthErrors: MinecraftAuthError[] = [
|
||||
{
|
||||
errorMatchers: ['Failed to deserialize response to JSON during step RefreshOAuthToken:'],
|
||||
whatHappened:
|
||||
'Your saved Microsoft sign-in token has expired or was revoked, so Modrinth App cannot refresh your Minecraft session.',
|
||||
stepsToFix: [
|
||||
'Sign out of the affected Minecraft account in Modrinth App',
|
||||
'Sign in to the account again',
|
||||
'Once the new sign-in finishes, try launching Minecraft again',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorMatchers: ['Failed to deserialize response to JSON during step SisuAuthenticate:'],
|
||||
whatHappened:
|
||||
'Xbox services rejected the first sign-in response. This is most often caused by your system clock or time zone being out of sync.',
|
||||
stepsToFix: [
|
||||
'Open your system date and time settings',
|
||||
'Turn on automatic time zone and automatic time, if available',
|
||||
'Use the sync option in your system settings to synchronize the clock',
|
||||
'Restart Modrinth App',
|
||||
'Try signing in again',
|
||||
],
|
||||
},
|
||||
{
|
||||
matches: (message) =>
|
||||
message.includes('Failed to deserialize response to JSON during step MinecraftToken:') &&
|
||||
message.includes('429 Too Many Requests'),
|
||||
whatHappened:
|
||||
'Microsoft or Minecraft temporarily blocked the sign-in request because there were too many recent attempts.',
|
||||
stepsToFix: [
|
||||
'Wait about an hour before trying again',
|
||||
'Restart Modrinth App after waiting',
|
||||
'Try signing in once more',
|
||||
'If the same message appears, wait longer before retrying so the temporary limit can clear',
|
||||
],
|
||||
},
|
||||
{
|
||||
matches: (message) =>
|
||||
message.includes('Failed to deserialize response to JSON during step MinecraftToken:') &&
|
||||
/Status Code: 5\d\d/.test(message),
|
||||
whatHappened:
|
||||
"Minecraft's authentication service is returning a server error, so Modrinth App cannot finish signing you in right now.",
|
||||
stepsToFix: [
|
||||
'Wait a few minutes and try signing in again',
|
||||
'Check <a href="https://support.xbox.com/xbox-live-status">Xbox Status</a> for current service issues',
|
||||
'Try signing in with the <a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a> to confirm whether Minecraft sign-in is also affected there',
|
||||
'If the service is healthy and this keeps happening, contact support with the debug information below',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorMatchers: ['Failed to fetch player profile'],
|
||||
whatHappened:
|
||||
'Minecraft services could not return a Java Edition profile for this account. This most often happens when the game was purchased recently, the Java profile has not finished being created, or the wrong Microsoft account is being used.',
|
||||
stepsToFix: [
|
||||
'Sign in with the <a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a>',
|
||||
'Launch Minecraft: Java Edition once from the official launcher',
|
||||
'Wait up to an hour if the purchase or profile setup was recent',
|
||||
'Make sure you are using the Microsoft account that owns Minecraft. See <a href="https://support.modrinth.com/en/articles/9409136-finding-the-right-xbox-account">Finding the right Xbox account</a> for help',
|
||||
'Try signing in to Modrinth App again',
|
||||
],
|
||||
},
|
||||
{
|
||||
matches: (message) =>
|
||||
message.includes('error sending request for url (') &&
|
||||
[
|
||||
'minecraft.net',
|
||||
'minecraftservices.com',
|
||||
'mojang.com',
|
||||
'xbox.com',
|
||||
'xboxlive.com',
|
||||
'live.com',
|
||||
].some((domain) => message.includes(domain)),
|
||||
whatHappened:
|
||||
'Modrinth App could not connect to a Microsoft, Xbox, or Minecraft service needed for sign-in. This is usually caused by a local network, DNS, proxy, firewall, hosts file, VPN, or antivirus issue.',
|
||||
stepsToFix: [
|
||||
'Restart Modrinth App and try signing in again',
|
||||
'Check that your internet connection is working',
|
||||
'Allow Modrinth App through your firewall, antivirus, proxy, VPN, and hosts file rules',
|
||||
'Try a different network or temporarily disable VPN/proxy software if you use one',
|
||||
'If routing or DNS is the issue, a service like Cloudflare WARP can sometimes help',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorCode: '2148916222',
|
||||
whatHappened:
|
||||
@@ -87,4 +170,31 @@ export const minecraftAuthErrors: MinecraftAuthError[] = [
|
||||
'Once finished, try signing in again',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorMatchers: ['Failed to deserialize response to JSON during step XstsAuthorize:'],
|
||||
whatHappened:
|
||||
'Xbox services rejected the request to authorize this account for Minecraft services, but did not return a specific account restriction that Modrinth App recognizes.',
|
||||
stepsToFix: [
|
||||
'Sign in with the <a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a>',
|
||||
'Complete any prompts shown by Microsoft, Xbox, or Minecraft',
|
||||
'Try signing in to Modrinth App again',
|
||||
'If the official launcher also fails, follow the error shown there or contact Xbox Support',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function findMinecraftAuthError(message: string): MinecraftAuthError | null {
|
||||
return (
|
||||
minecraftAuthErrors.find((error) => {
|
||||
if (error.errorCode && message.includes(error.errorCode)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (error.errorMatchers?.some((matcher) => message.includes(matcher))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return error.matches?.(message) ?? false
|
||||
}) ?? null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -148,7 +148,6 @@ import { arrayBufferToBase64 } from '@modrinth/utils'
|
||||
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import {
|
||||
add_and_equip_custom_skin,
|
||||
type Cape,
|
||||
determineModelType,
|
||||
equip_skin,
|
||||
@@ -440,9 +439,22 @@ async function save() {
|
||||
const bytes: Uint8Array = new Uint8Array(await (await fetch(textureUrl)).arrayBuffer())
|
||||
|
||||
if (mode.value === 'new') {
|
||||
const addedSkin = await add_and_equip_custom_skin(bytes, variant.value, selectedCape.value)
|
||||
const addedSkin = await save_custom_skin(
|
||||
{
|
||||
texture_key: '',
|
||||
variant: variant.value,
|
||||
cape_id: selectedCape.value?.id,
|
||||
texture: textureUrl,
|
||||
source: 'custom',
|
||||
is_equipped: false,
|
||||
},
|
||||
bytes,
|
||||
variant.value,
|
||||
selectedCape.value,
|
||||
true,
|
||||
)
|
||||
emit('saved', {
|
||||
applied: true,
|
||||
applied: false,
|
||||
skin: addedSkin,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useElementSize, useWindowSize } from '@vueuse/core'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import { computed, nextTick, onUnmounted, ref, useTemplateRef, watch } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import type { RenderResult } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import type { Skin } from '@/helpers/skins.ts'
|
||||
@@ -82,12 +83,14 @@ const props = defineProps<{
|
||||
isSkinSelected: (skin: Skin) => boolean
|
||||
isSkinActive: (skin: Skin) => boolean
|
||||
isAddSkinButtonDragActive: boolean
|
||||
readOnly?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [skin: Skin]
|
||||
edit: [skin: Skin, event: MouseEvent]
|
||||
delete: [skin: Skin]
|
||||
'reorder-saved-skins': [skins: Skin[]]
|
||||
'add-skin': []
|
||||
'add-skin-dragenter': [event: DragEvent]
|
||||
'add-skin-dragover': [event: DragEvent]
|
||||
@@ -153,6 +156,11 @@ const sections = computed<SkinSection[]>(() => [
|
||||
})),
|
||||
])
|
||||
|
||||
const draggableSavedSkins = ref<Skin[]>([])
|
||||
const isDraggingSavedSkin = ref(false)
|
||||
const canReorderSavedSkins = computed(() => draggableSavedSkins.value.length > 1)
|
||||
const fixedSavedSkins = computed(() => props.savedSkins.filter((skin) => !canDragSavedSkin(skin)))
|
||||
|
||||
const sectionLayouts = computed(() => {
|
||||
const layouts: Array<{ section: SkinSection; top: number; height: number; index: number }> = []
|
||||
let top = 0
|
||||
@@ -209,6 +217,18 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.savedSkins,
|
||||
(nextSkins) => {
|
||||
if (isDraggingSavedSkin.value) {
|
||||
return
|
||||
}
|
||||
|
||||
draggableSavedSkins.value = nextSkins.filter(canDragSavedSkin)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
listWidth,
|
||||
(width) => {
|
||||
@@ -257,6 +277,40 @@ function skinKey(skin: Skin, prefix: string) {
|
||||
return `${prefix}-${skin.source}-${skin.texture_key}-${skin.variant}-${skin.cape_id ?? 'no-cape'}`
|
||||
}
|
||||
|
||||
function savedSkinKey(skin: Skin) {
|
||||
return skinKey(skin, 'saved-skin')
|
||||
}
|
||||
|
||||
function canDragSavedSkin(skin: Skin) {
|
||||
return skin.source === 'custom' || skin.source === 'custom_external'
|
||||
}
|
||||
|
||||
function doSkinOrdersMatch(firstSkins: Skin[], secondSkins: Skin[]) {
|
||||
const draggableSecondSkins = secondSkins.filter(canDragSavedSkin)
|
||||
|
||||
return (
|
||||
firstSkins.length === draggableSecondSkins.length &&
|
||||
firstSkins.every(
|
||||
(skin, index) => savedSkinKey(skin) === savedSkinKey(draggableSecondSkins[index]),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function onSavedSkinDragStart() {
|
||||
isDraggingSavedSkin.value = true
|
||||
}
|
||||
|
||||
function onSavedSkinDragEnd() {
|
||||
isDraggingSavedSkin.value = false
|
||||
|
||||
if (doSkinOrdersMatch(draggableSavedSkins.value, props.savedSkins)) {
|
||||
draggableSavedSkins.value = props.savedSkins.filter(canDragSavedSkin)
|
||||
return
|
||||
}
|
||||
|
||||
emit('reorder-saved-skins', [...draggableSavedSkins.value])
|
||||
}
|
||||
|
||||
function isSectionOpen(key: string) {
|
||||
return openSectionKeys.value.has(key)
|
||||
}
|
||||
@@ -354,36 +408,140 @@ defineExpose({ getAddSkinButtonElement })
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
||||
<div
|
||||
<Draggable
|
||||
v-if="section.kind === 'saved'"
|
||||
:list="draggableSavedSkins"
|
||||
class="grid w-full grid-cols-3 gap-3 min-[1300px]:grid-cols-4 min-[1750px]:grid-cols-5 min-[2050px]:grid-cols-6"
|
||||
:item-key="savedSkinKey"
|
||||
:disabled="readOnly || !canReorderSavedSkins"
|
||||
:animation="250"
|
||||
:swap-threshold="1"
|
||||
:invert-swap="false"
|
||||
:force-fallback="true"
|
||||
:fallback-on-body="true"
|
||||
:fallback-tolerance="4"
|
||||
ghost-class="skin-reorder-ghost"
|
||||
chosen-class="skin-reorder-chosen"
|
||||
drag-class="skin-reorder-drag"
|
||||
fallback-class="skin-reorder-fallback"
|
||||
@start="onSavedSkinDragStart"
|
||||
@end="onSavedSkinDragEnd"
|
||||
>
|
||||
<template #header>
|
||||
<SkinLikeTextButton
|
||||
ref="addSkinButton"
|
||||
class="aspect-[31/40] w-full min-w-0 box-border rounded-[20px]"
|
||||
dropzone
|
||||
:disabled="readOnly"
|
||||
:drag-active="!readOnly && isAddSkinButtonDragActive"
|
||||
@click="emit('add-skin')"
|
||||
@dragenter="emit('add-skin-dragenter', $event)"
|
||||
@dragover="emit('add-skin-dragover', $event)"
|
||||
@dragleave="emit('add-skin-dragleave', $event)"
|
||||
@drop="emit('add-skin-drop', $event)"
|
||||
>
|
||||
<template #icon>
|
||||
<PlusIcon class="size-8" />
|
||||
</template>
|
||||
{{ formatMessage(messages.addSkinButton) }}
|
||||
<template #subtitle>{{ formatMessage(messages.dragAndDropSubtitle) }}</template>
|
||||
</SkinLikeTextButton>
|
||||
</template>
|
||||
|
||||
<template #item="{ element: skin }">
|
||||
<div
|
||||
:key="savedSkinKey(skin)"
|
||||
class="relative aspect-[31/40] w-full min-w-0 box-border rounded-[20px]"
|
||||
>
|
||||
<SkinButton
|
||||
class="h-full w-full min-w-0 box-border rounded-[20px]"
|
||||
:forward-image-src="getBakedSkinTextures(skin)?.forwards"
|
||||
:selected="isSkinSelected(skin)"
|
||||
:active="isSkinActive(skin)"
|
||||
:disabled="readOnly"
|
||||
:is-dragging="isDraggingSavedSkin"
|
||||
@select="emit('select', skin)"
|
||||
>
|
||||
<template v-if="!readOnly" #overlay-buttons>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:aria-label="formatMessage(messages.editSkinButton)"
|
||||
class="pointer-events-auto"
|
||||
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
|
||||
>
|
||||
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-show="!skin.is_equipped" circular color="red">
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.deleteSkinButton)"
|
||||
:aria-label="formatMessage(messages.deleteSkinButton)"
|
||||
class="!rounded-[100%] pointer-events-auto"
|
||||
@click.stop="emit('delete', skin)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</SkinButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div
|
||||
v-for="skin in fixedSavedSkins"
|
||||
:key="savedSkinKey(skin)"
|
||||
class="relative aspect-[31/40] w-full min-w-0 box-border rounded-[20px]"
|
||||
>
|
||||
<SkinButton
|
||||
class="h-full w-full min-w-0 box-border rounded-[20px]"
|
||||
:forward-image-src="getBakedSkinTextures(skin)?.forwards"
|
||||
:selected="isSkinSelected(skin)"
|
||||
:active="isSkinActive(skin)"
|
||||
:disabled="readOnly"
|
||||
:is-dragging="isDraggingSavedSkin"
|
||||
@select="emit('select', skin)"
|
||||
>
|
||||
<template v-if="!readOnly" #overlay-buttons>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:aria-label="formatMessage(messages.editSkinButton)"
|
||||
class="pointer-events-auto"
|
||||
@click.stop="(event: MouseEvent) => emit('edit', skin, event)"
|
||||
>
|
||||
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-show="!skin.is_equipped" circular color="red">
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.deleteSkinButton)"
|
||||
:aria-label="formatMessage(messages.deleteSkinButton)"
|
||||
class="!rounded-[100%] pointer-events-auto"
|
||||
@click.stop="emit('delete', skin)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</SkinButton>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="grid w-full grid-cols-3 gap-3 min-[1300px]:grid-cols-4 min-[1750px]:grid-cols-5 min-[2050px]:grid-cols-6"
|
||||
>
|
||||
<SkinLikeTextButton
|
||||
ref="addSkinButton"
|
||||
class="aspect-[31/40] w-full min-w-0 box-border rounded-[20px]"
|
||||
dropzone
|
||||
:drag-active="isAddSkinButtonDragActive"
|
||||
@click="emit('add-skin')"
|
||||
@dragenter="emit('add-skin-dragenter', $event)"
|
||||
@dragover="emit('add-skin-dragover', $event)"
|
||||
@dragleave="emit('add-skin-dragleave', $event)"
|
||||
@drop="emit('add-skin-drop', $event)"
|
||||
>
|
||||
<template #icon>
|
||||
<PlusIcon class="size-8" />
|
||||
</template>
|
||||
{{ formatMessage(messages.addSkinButton) }}
|
||||
<template #subtitle>{{ formatMessage(messages.dragAndDropSubtitle) }}</template>
|
||||
</SkinLikeTextButton>
|
||||
|
||||
<SkinButton
|
||||
v-for="skin in section.skins"
|
||||
:key="skinKey(skin, 'saved-skin')"
|
||||
:key="skinKey(skin, section.key)"
|
||||
class="aspect-[31/40] w-full min-w-0 box-border rounded-[20px]"
|
||||
:forward-image-src="getBakedSkinTextures(skin)?.forwards"
|
||||
:backward-image-src="getBakedSkinTextures(skin)?.backwards"
|
||||
:selected="isSkinSelected(skin)"
|
||||
:active="isSkinActive(skin)"
|
||||
:tooltip="skin.name"
|
||||
:disabled="readOnly"
|
||||
:is-dragging="isDraggingSavedSkin"
|
||||
@select="emit('select', skin)"
|
||||
>
|
||||
<template #overlay-buttons>
|
||||
@@ -396,37 +554,25 @@ defineExpose({ getAddSkinButtonElement })
|
||||
<EditIcon /> {{ formatMessage(commonMessages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-show="!skin.is_equipped" circular color="red">
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.deleteSkinButton)"
|
||||
:aria-label="formatMessage(messages.deleteSkinButton)"
|
||||
class="!rounded-[100%] pointer-events-auto"
|
||||
@click.stop="emit('delete', skin)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</SkinButton>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="grid w-full grid-cols-3 gap-3 min-[1300px]:grid-cols-4 min-[1750px]:grid-cols-5 min-[2050px]:grid-cols-6"
|
||||
>
|
||||
<SkinButton
|
||||
v-for="skin in section.skins"
|
||||
:key="skinKey(skin, section.key)"
|
||||
class="aspect-[31/40] w-full min-w-0 box-border rounded-[20px]"
|
||||
:forward-image-src="getBakedSkinTextures(skin)?.forwards"
|
||||
:backward-image-src="getBakedSkinTextures(skin)?.backwards"
|
||||
:selected="isSkinSelected(skin)"
|
||||
:active="isSkinActive(skin)"
|
||||
:tooltip="skin.name"
|
||||
@select="emit('select', skin)"
|
||||
/>
|
||||
</div>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:global(.skin-reorder-ghost) {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
:global(.skin-reorder-drag) {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
:global(.skin-reorder-fallback) {
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -197,11 +197,13 @@ export async function add_project_from_version(
|
||||
path: string,
|
||||
versionId: string,
|
||||
reason: DownloadReason,
|
||||
dependentOnVersionId?: string,
|
||||
): Promise<string> {
|
||||
return await invoke('plugin:profile|profile_add_project_from_version', {
|
||||
path,
|
||||
versionId,
|
||||
reason,
|
||||
dependentOnVersionId,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,10 @@ import { skinPreviewStorage } from '../storage/skin-preview-storage'
|
||||
|
||||
export interface RenderResult {
|
||||
forwards: string
|
||||
backwards: string
|
||||
}
|
||||
|
||||
export interface RawRenderResult {
|
||||
forwards: Blob
|
||||
backwards: Blob
|
||||
}
|
||||
|
||||
class BatchSkinRenderer {
|
||||
@@ -92,12 +90,9 @@ class BatchSkinRenderer {
|
||||
}
|
||||
|
||||
const frontCameraPos: [number, number, number] = [-1.3, 1, 6.3]
|
||||
const backCameraPos: [number, number, number] = [-1.3, 1, -2.5]
|
||||
|
||||
const forwards = await this.renderView(frontCameraPos, lookAtTarget)
|
||||
const backwards = await this.renderView(backCameraPos, lookAtTarget)
|
||||
|
||||
return { forwards, backwards }
|
||||
return { forwards }
|
||||
}
|
||||
|
||||
private async renderView(
|
||||
@@ -404,16 +399,15 @@ async function generateSkinPreviewsForGeneration(
|
||||
const headKey = headKeys[i]
|
||||
|
||||
const rawCached = cachedSkinPreviews[skinKey]
|
||||
if (rawCached) {
|
||||
if (rawCached && !skinBlobUrlMap.has(skinKey)) {
|
||||
const cached: RenderResult = {
|
||||
forwards: URL.createObjectURL(rawCached.forwards),
|
||||
backwards: URL.createObjectURL(rawCached.backwards),
|
||||
}
|
||||
skinBlobUrlMap.set(skinKey, cached)
|
||||
}
|
||||
|
||||
const cachedHead = cachedHeadPreviews[headKey]
|
||||
if (cachedHead) {
|
||||
if (cachedHead && !headBlobUrlMap.has(headKey)) {
|
||||
headBlobUrlMap.set(headKey, URL.createObjectURL(cachedHead))
|
||||
}
|
||||
}
|
||||
@@ -427,7 +421,6 @@ async function generateSkinPreviewsForGeneration(
|
||||
if (DEBUG_MODE) {
|
||||
const result = skinBlobUrlMap.get(key)!
|
||||
URL.revokeObjectURL(result.forwards)
|
||||
URL.revokeObjectURL(result.backwards)
|
||||
skinBlobUrlMap.delete(key)
|
||||
} else continue
|
||||
}
|
||||
@@ -456,7 +449,6 @@ async function generateSkinPreviewsForGeneration(
|
||||
|
||||
const renderResult: RenderResult = {
|
||||
forwards: URL.createObjectURL(rawRenderResult.forwards),
|
||||
backwards: URL.createObjectURL(rawRenderResult.backwards),
|
||||
}
|
||||
|
||||
skinBlobUrlMap.set(key, renderResult)
|
||||
|
||||
@@ -142,6 +142,12 @@ export async function remove_custom_skin(skin: Skin): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
export async function set_custom_skin_order(textureKeys: string[]): Promise<void> {
|
||||
await invoke('plugin:minecraft-skins|set_custom_skin_order', {
|
||||
textureKeys,
|
||||
})
|
||||
}
|
||||
|
||||
export async function save_custom_skin(
|
||||
skin: Skin,
|
||||
textureBlob: Uint8Array,
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { RawRenderResult } from '../rendering/batch-skin-renderer'
|
||||
|
||||
interface StoredPreview {
|
||||
forwards: Blob
|
||||
backwards: Blob
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
@@ -38,7 +37,6 @@ export class SkinPreviewStorage {
|
||||
|
||||
const storedPreview: StoredPreview = {
|
||||
forwards: result.forwards,
|
||||
backwards: result.backwards,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ export class SkinPreviewStorage {
|
||||
return
|
||||
}
|
||||
|
||||
resolve({ forwards: result.forwards, backwards: result.backwards })
|
||||
resolve({ forwards: result.forwards })
|
||||
}
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
@@ -95,7 +93,7 @@ export class SkinPreviewStorage {
|
||||
const result = request.result as StoredPreview | undefined
|
||||
|
||||
if (result) {
|
||||
results[key] = { forwards: result.forwards, backwards: result.backwards }
|
||||
results[key] = { forwards: result.forwards }
|
||||
} else {
|
||||
results[key] = null
|
||||
}
|
||||
@@ -173,7 +171,7 @@ export class SkinPreviewStorage {
|
||||
const key = cursor.primaryKey as string
|
||||
const value = cursor.value as StoredPreview
|
||||
|
||||
const entrySize = value.forwards.size + value.backwards.size
|
||||
const entrySize = value.forwards.size
|
||||
totalSize += entrySize
|
||||
count++
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -437,6 +437,12 @@
|
||||
"app.skins.rate-limit.title": {
|
||||
"message": "Slow down!"
|
||||
},
|
||||
"app.skins.reorder-error.text": {
|
||||
"message": "Your skin order could not be saved."
|
||||
},
|
||||
"app.skins.reorder-error.title": {
|
||||
"message": "Failed to reorder skins"
|
||||
},
|
||||
"app.skins.section.builders-and-biomes": {
|
||||
"message": "Builders & Biomes"
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -30,7 +30,7 @@ import type AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import EditSkinModal from '@/components/ui/skin/EditSkinModal.vue'
|
||||
import VirtualSkinSectionList from '@/components/ui/skin/VirtualSkinSectionList.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_default_user, login as login_flow, users } from '@/helpers/auth'
|
||||
import { check_reachable, get_default_user, login as login_flow, users } from '@/helpers/auth'
|
||||
import type { RenderResult } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import { generateSkinPreviews, skinBlobUrlMap } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import type { Cape, Skin, SkinTextureUrl } from '@/helpers/skins.ts'
|
||||
@@ -46,6 +46,8 @@ import {
|
||||
get_normalized_skin_texture,
|
||||
normalize_skin_texture,
|
||||
remove_custom_skin,
|
||||
save_custom_skin,
|
||||
set_custom_skin_order,
|
||||
} from '@/helpers/skins.ts'
|
||||
import { hasPride26Badge } from '@/helpers/user-campaigns.ts'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
@@ -129,6 +131,14 @@ const messages = defineMessages({
|
||||
id: 'app.skins.dropped-file-error.text',
|
||||
defaultMessage: 'Failed to read the dropped file.',
|
||||
},
|
||||
reorderSkinErrorTitle: {
|
||||
id: 'app.skins.reorder-error.title',
|
||||
defaultMessage: 'Failed to reorder skins',
|
||||
},
|
||||
reorderSkinErrorText: {
|
||||
id: 'app.skins.reorder-error.text',
|
||||
defaultMessage: 'Your skin order could not be saved.',
|
||||
},
|
||||
deleteSkinTitle: {
|
||||
id: 'app.skins.delete-modal.title',
|
||||
defaultMessage: 'Are you sure you want to delete this skin?',
|
||||
@@ -181,6 +191,7 @@ const client = injectModrinthClient()
|
||||
const themeStore = useTheming()
|
||||
const skins = ref<Skin[]>([])
|
||||
const capes = ref<Cape[]>([])
|
||||
const offline = ref(!navigator.onLine)
|
||||
|
||||
const accountsCard = inject('accountsCard') as Ref<typeof AccountsCard>
|
||||
const currentUser = ref(undefined)
|
||||
@@ -200,6 +211,16 @@ const savedSkins = computed(() => {
|
||||
return []
|
||||
}
|
||||
})
|
||||
const authServerQuery = useQuery({
|
||||
queryKey: ['authServerReachability'],
|
||||
queryFn: async () => {
|
||||
await check_reachable()
|
||||
return true
|
||||
},
|
||||
refetchInterval: 5 * 60 * 1000,
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
const { data: modrinthUser } = useQuery({
|
||||
queryKey: computed(() => ['authenticated-user', 'campaigns', auth.user.value?.id]),
|
||||
queryFn: () => client.labrinth.users_v3.getAuthenticated(),
|
||||
@@ -249,8 +270,18 @@ const currentCape = computed(() => {
|
||||
})
|
||||
|
||||
const skinTexture = computedAsync(async () => {
|
||||
if (selectedSkin.value?.texture) {
|
||||
return await get_normalized_skin_texture(selectedSkin.value)
|
||||
const skin = selectedSkin.value
|
||||
if (skin?.texture) {
|
||||
try {
|
||||
return await get_normalized_skin_texture(skin)
|
||||
} catch (error) {
|
||||
if (skin.texture.startsWith('data:image/')) {
|
||||
return skin.texture
|
||||
}
|
||||
|
||||
handleError(error as Error)
|
||||
return ''
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
@@ -258,6 +289,9 @@ const skinTexture = computedAsync(async () => {
|
||||
const capeTexture = computed(() => currentCape.value?.texture)
|
||||
const skinVariant = computed(() => selectedSkin.value?.variant)
|
||||
const skinNametag = computed(() => (themeStore.hideNametagSkinsPage ? undefined : username.value))
|
||||
const isSkinManagementReadOnly = computed(
|
||||
() => offline.value || (authServerQuery.isError.value && !authServerQuery.isLoading.value),
|
||||
)
|
||||
const hasPendingSkinChange = computed(
|
||||
() => !skinsMatch(selectedSkin.value, originalSelectedSkin.value),
|
||||
)
|
||||
@@ -274,11 +308,15 @@ const deleteSkinModal = ref()
|
||||
const skinToDelete = ref<Skin | null>(null)
|
||||
|
||||
function confirmDeleteSkin(skin: Skin) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
skinToDelete.value = skin
|
||||
deleteSkinModal.value?.show()
|
||||
}
|
||||
|
||||
async function deleteSkin() {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
const deletedSkin = skinToDelete.value
|
||||
if (!deletedSkin) return
|
||||
|
||||
@@ -304,7 +342,23 @@ async function loadCapes() {
|
||||
|
||||
async function loadSkins() {
|
||||
try {
|
||||
skins.value = (await get_available_skins()) ?? []
|
||||
const loadedSkins = (await get_available_skins()) ?? []
|
||||
const loadedEquippedSkin = loadedSkins.find((s) => s.is_equipped)
|
||||
const locallyKnownEquippedSkin =
|
||||
originalSelectedSkin.value &&
|
||||
(loadedSkins.find((skin) => skinsMatch(skin, originalSelectedSkin.value)) ??
|
||||
(originalSelectedSkin.value.texture.startsWith('data:image/')
|
||||
? originalSelectedSkin.value
|
||||
: undefined))
|
||||
const shouldPreserveKnownEquippedSkin =
|
||||
isSkinManagementReadOnly.value &&
|
||||
locallyKnownEquippedSkin &&
|
||||
!skinsMatch(loadedEquippedSkin, locallyKnownEquippedSkin)
|
||||
|
||||
skins.value =
|
||||
shouldPreserveKnownEquippedSkin && locallyKnownEquippedSkin
|
||||
? mergeEquippedSkin(loadedSkins, locallyKnownEquippedSkin)
|
||||
: loadedSkins
|
||||
generateSkinPreviews(skins.value, capes.value)
|
||||
selectedSkin.value = skins.value.find((s) => s.is_equipped) ?? null
|
||||
originalSelectedSkin.value = selectedSkin.value
|
||||
@@ -315,6 +369,28 @@ async function loadSkins() {
|
||||
}
|
||||
}
|
||||
|
||||
function mergeEquippedSkin(list: Skin[], equippedSkin: Skin) {
|
||||
let foundEquippedSkin = false
|
||||
const mergedSkins = list.map((skin) => {
|
||||
const isEquipped = skinsMatch(skin, equippedSkin)
|
||||
foundEquippedSkin ||= isEquipped
|
||||
|
||||
return {
|
||||
...skin,
|
||||
is_equipped: isEquipped,
|
||||
}
|
||||
})
|
||||
|
||||
if (!foundEquippedSkin) {
|
||||
mergedSkins.unshift({
|
||||
...equippedSkin,
|
||||
is_equipped: true,
|
||||
})
|
||||
}
|
||||
|
||||
return mergedSkins
|
||||
}
|
||||
|
||||
function skinsMatch(a?: Skin | null, b?: Skin | null) {
|
||||
return (
|
||||
a?.source === b?.source &&
|
||||
@@ -324,6 +400,14 @@ function skinsMatch(a?: Skin | null, b?: Skin | null) {
|
||||
)
|
||||
}
|
||||
|
||||
function skinsMatchIgnoringSource(a?: Skin | null, b?: Skin | null) {
|
||||
return (
|
||||
a?.texture_key === b?.texture_key &&
|
||||
a?.variant === b?.variant &&
|
||||
(a?.cape_id ?? null) === (b?.cape_id ?? null)
|
||||
)
|
||||
}
|
||||
|
||||
function isSkinSelected(skin: Skin) {
|
||||
return skinsMatch(selectedSkin.value, skin)
|
||||
}
|
||||
@@ -385,6 +469,8 @@ function getDefaultSkinSectionSortIndex(section: string) {
|
||||
}
|
||||
|
||||
function changeSkin(newSkin: Skin) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
selectedSkin.value = newSkin
|
||||
}
|
||||
|
||||
@@ -423,6 +509,19 @@ function setLocallyEquippedSkin(skinToApply: Skin) {
|
||||
void accountsCard.value?.setEquippedSkin(originalSelectedSkin.value)
|
||||
}
|
||||
|
||||
function insertLocalSkin(savedSkin: Skin) {
|
||||
const firstNonCustomSkinIndex = skins.value.findIndex((skin) => skin.source !== 'custom')
|
||||
|
||||
if (firstNonCustomSkinIndex === -1) {
|
||||
skins.value = [...skins.value, savedSkin]
|
||||
return
|
||||
}
|
||||
|
||||
const nextSkins = [...skins.value]
|
||||
nextSkins.splice(firstNonCustomSkinIndex, 0, savedSkin)
|
||||
skins.value = nextSkins
|
||||
}
|
||||
|
||||
function updateLocalSkin(savedSkin: Skin, applied: boolean, previousSkin?: Skin) {
|
||||
let foundSkin = false
|
||||
const replacesSelectedSkin =
|
||||
@@ -451,7 +550,7 @@ function updateLocalSkin(savedSkin: Skin, applied: boolean, previousSkin?: Skin)
|
||||
})
|
||||
|
||||
if (!foundSkin) {
|
||||
skins.value.unshift({
|
||||
insertLocalSkin({
|
||||
...savedSkin,
|
||||
is_equipped: applied || savedSkin.is_equipped,
|
||||
})
|
||||
@@ -480,6 +579,81 @@ function updateLocalSkin(savedSkin: Skin, applied: boolean, previousSkin?: Skin)
|
||||
generateSkinPreviews(skins.value, capes.value)
|
||||
}
|
||||
|
||||
async function reorderSavedSkins(orderedSkins: Skin[]) {
|
||||
const previousSkins = skins.value
|
||||
const previousSelectedSkin = selectedSkin.value
|
||||
const previousOriginalSelectedSkin = originalSelectedSkin.value
|
||||
const orderedTextureKeys = orderedSkins.map((skin) => skin.texture_key)
|
||||
const orderedTextureKeySet = new Set(orderedTextureKeys)
|
||||
const remainingSavedSkins = previousSkins.filter(
|
||||
(skin) => skin.source !== 'default' && !orderedTextureKeySet.has(skin.texture_key),
|
||||
)
|
||||
const defaultSkins = previousSkins.filter((skin) => skin.source === 'default')
|
||||
const nextSavedSkins = [...orderedSkins, ...remainingSavedSkins]
|
||||
|
||||
skins.value = [...nextSavedSkins, ...defaultSkins]
|
||||
generateSkinPreviews(skins.value, capes.value)
|
||||
|
||||
try {
|
||||
const persistedSavedSkins = await preserveExternalSkins(nextSavedSkins)
|
||||
|
||||
if (persistedSavedSkins.some((skin, index) => skin !== nextSavedSkins[index])) {
|
||||
skins.value = [...persistedSavedSkins, ...defaultSkins]
|
||||
generateSkinPreviews(skins.value, capes.value)
|
||||
}
|
||||
|
||||
await set_custom_skin_order(
|
||||
persistedSavedSkins
|
||||
.filter((skin) => skin.source === 'custom')
|
||||
.map((skin) => skin.texture_key),
|
||||
)
|
||||
} catch (error) {
|
||||
skins.value = previousSkins
|
||||
selectedSkin.value = previousSelectedSkin
|
||||
originalSelectedSkin.value = previousOriginalSelectedSkin
|
||||
generateSkinPreviews(skins.value, capes.value)
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: formatMessage(messages.reorderSkinErrorTitle),
|
||||
text: error instanceof Error ? error.message : formatMessage(messages.reorderSkinErrorText),
|
||||
})
|
||||
await loadSkins()
|
||||
}
|
||||
}
|
||||
|
||||
async function preserveExternalSkins(skinsToPersist: Skin[]) {
|
||||
const preservedSkins: Skin[] = []
|
||||
|
||||
for (const skin of skinsToPersist) {
|
||||
if (skin.source !== 'custom_external') {
|
||||
preservedSkins.push(skin)
|
||||
continue
|
||||
}
|
||||
|
||||
const textureBlob = await normalize_skin_texture(skin.texture)
|
||||
const capeId = skin.cape_id ? capes.value.find((cape) => cape.id === skin.cape_id) : undefined
|
||||
const savedSkin = await save_custom_skin(skin, textureBlob, skin.variant, capeId, false)
|
||||
const preservedSkin: Skin = {
|
||||
...savedSkin,
|
||||
source: 'custom',
|
||||
is_equipped: skin.is_equipped,
|
||||
}
|
||||
|
||||
if (skinsMatchIgnoringSource(selectedSkin.value, skin)) {
|
||||
selectedSkin.value = preservedSkin
|
||||
}
|
||||
|
||||
if (skinsMatchIgnoringSource(originalSelectedSkin.value, skin)) {
|
||||
originalSelectedSkin.value = preservedSkin
|
||||
void accountsCard.value?.setEquippedSkin(preservedSkin)
|
||||
}
|
||||
|
||||
preservedSkins.push(preservedSkin)
|
||||
}
|
||||
|
||||
return preservedSkins
|
||||
}
|
||||
|
||||
function schedulePendingSkinRefresh() {
|
||||
if (pendingSkinRefreshTimeout !== null) {
|
||||
window.clearTimeout(pendingSkinRefreshTimeout)
|
||||
@@ -517,7 +691,13 @@ function schedulePendingSkinRefresh() {
|
||||
|
||||
async function applySelectedSkin() {
|
||||
const skinToApply = selectedSkin.value
|
||||
if (!skinToApply || !hasPendingSkinChange.value || isApplyingSkin.value) return
|
||||
if (
|
||||
!skinToApply ||
|
||||
!hasPendingSkinChange.value ||
|
||||
isApplyingSkin.value ||
|
||||
isSkinManagementReadOnly.value
|
||||
)
|
||||
return
|
||||
|
||||
isApplyingSkin.value = true
|
||||
try {
|
||||
@@ -586,10 +766,14 @@ async function login() {
|
||||
}
|
||||
|
||||
function openAddSkinFileBrowser() {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
addSkinFileInput.value?.click()
|
||||
}
|
||||
|
||||
async function onAddSkinFileInputChange(e: Event) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
const file = files?.[0]
|
||||
|
||||
@@ -632,6 +816,8 @@ function isPositionOverAddSkinButton(position: { x: number; y: number }) {
|
||||
}
|
||||
|
||||
async function handleAddSkinNativeDragDrop(event: { payload: DragDropEvent }) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
const payload = event.payload
|
||||
|
||||
if (payload.type === 'leave') {
|
||||
@@ -680,6 +866,8 @@ async function handleAddSkinNativeDragDrop(event: { payload: DragDropEvent }) {
|
||||
}
|
||||
|
||||
function onAddSkinDragOver(event: DragEvent) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
if (!isSkinFileDrag(event)) {
|
||||
return
|
||||
}
|
||||
@@ -688,10 +876,14 @@ function onAddSkinDragOver(event: DragEvent) {
|
||||
}
|
||||
|
||||
function onAddSkinDragLeave() {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
isAddSkinButtonDragActive.value = false
|
||||
}
|
||||
|
||||
async function onAddSkinDrop(event: DragEvent) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
isAddSkinButtonDragActive.value = false
|
||||
|
||||
const file = Array.from(event.dataTransfer?.files ?? []).find(
|
||||
@@ -721,6 +913,8 @@ async function setupAddSkinDragDropListener() {
|
||||
}
|
||||
|
||||
async function processSkinFileBuffer(buffer: Uint8Array | ArrayBuffer) {
|
||||
if (isSkinManagementReadOnly.value) return
|
||||
|
||||
const fakeEvent = new MouseEvent('click')
|
||||
const originalSkinTexUrl = `data:image/png;base64,` + arrayBufferToBase64(buffer)
|
||||
try {
|
||||
@@ -740,13 +934,24 @@ watch(
|
||||
() => {},
|
||||
)
|
||||
|
||||
watch(isSkinManagementReadOnly, (readOnly) => {
|
||||
if (readOnly) {
|
||||
isDraggingSkinFile.value = false
|
||||
isAddSkinButtonDragActive.value = false
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('offline', onOffline)
|
||||
window.addEventListener('online', onOnline)
|
||||
userCheckInterval = window.setInterval(checkUserChanges, 250)
|
||||
void setupAddSkinDragDropListener()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
isUnmounted = true
|
||||
window.removeEventListener('offline', onOffline)
|
||||
window.removeEventListener('online', onOnline)
|
||||
|
||||
if (userCheckInterval !== null) {
|
||||
window.clearInterval(userCheckInterval)
|
||||
@@ -763,6 +968,15 @@ onUnmounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
function onOffline() {
|
||||
offline.value = true
|
||||
}
|
||||
|
||||
function onOnline() {
|
||||
offline.value = false
|
||||
void authServerQuery.refetch()
|
||||
}
|
||||
|
||||
async function checkUserChanges() {
|
||||
try {
|
||||
const defaultId = await get_default_user()
|
||||
@@ -834,7 +1048,7 @@ await loadSkins()
|
||||
>
|
||||
<button
|
||||
class="flex h-10 min-w-0 cursor-pointer items-center justify-center gap-2 rounded-[14px] border-0 bg-surface-4 px-4 py-2.5 text-base font-semibold leading-5 text-contrast shadow-md transition-[filter,transform] duration-200 enabled:hover:brightness-[--hover-brightness] enabled:focus-visible:brightness-[--hover-brightness] enabled:active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-5 [&>svg]:shrink-0"
|
||||
:disabled="isApplyingSkin"
|
||||
:disabled="isApplyingSkin || isSkinManagementReadOnly"
|
||||
@click="resetSelectedSkin"
|
||||
>
|
||||
<RotateCounterClockwiseIcon />
|
||||
@@ -842,7 +1056,7 @@ await loadSkins()
|
||||
</button>
|
||||
<button
|
||||
class="flex h-10 min-w-0 cursor-pointer items-center justify-center gap-2 rounded-[14px] border-0 bg-brand px-4 py-2.5 text-base font-semibold leading-5 text-[rgba(0,0,0,0.9)] shadow-md transition-[filter,transform] duration-200 enabled:hover:brightness-[--hover-brightness] enabled:focus-visible:brightness-[--hover-brightness] enabled:active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-5 [&>svg]:shrink-0"
|
||||
:disabled="isApplyingSkin"
|
||||
:disabled="isApplyingSkin || isSkinManagementReadOnly"
|
||||
@click="applySelectedSkin"
|
||||
>
|
||||
<SpinnerIcon v-if="isApplyingSkin" class="animate-spin" />
|
||||
@@ -853,7 +1067,7 @@ await loadSkins()
|
||||
<button
|
||||
v-else
|
||||
class="flex h-10 min-w-0 cursor-pointer items-center justify-center gap-2 rounded-[14px] border-0 bg-surface-4 px-4 py-2.5 text-base font-semibold leading-5 shadow-md transition-[filter,transform] duration-200 enabled:hover:brightness-[--hover-brightness] enabled:focus-visible:brightness-[--hover-brightness] enabled:active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-5 [&>svg]:shrink-0"
|
||||
:disabled="!selectedSkin"
|
||||
:disabled="!selectedSkin || isSkinManagementReadOnly"
|
||||
@click="(e: MouseEvent) => selectedSkin && editSkinModal?.show(e, selectedSkin)"
|
||||
>
|
||||
<EditIcon />
|
||||
@@ -873,9 +1087,11 @@ await loadSkins()
|
||||
:is-skin-selected="isSkinSelected"
|
||||
:is-skin-active="isSkinActive"
|
||||
:is-add-skin-button-drag-active="isAddSkinButtonDragActive"
|
||||
:read-only="isSkinManagementReadOnly"
|
||||
@select="changeSkin"
|
||||
@edit="(skin, event) => editSkinModal?.show(event, skin)"
|
||||
@delete="confirmDeleteSkin"
|
||||
@reorder-saved-skins="reorderSavedSkins"
|
||||
@add-skin="openAddSkinFileBrowser"
|
||||
@add-skin-dragenter="onAddSkinDragOver"
|
||||
@add-skin-dragover="onAddSkinDragOver"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -248,7 +248,6 @@ export default new createRouter({
|
||||
component: Instance.Logs,
|
||||
meta: {
|
||||
useRootContext: true,
|
||||
// renderMode: 'fixed',
|
||||
breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Logs' }],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import { findMinecraftAuthError } from '@/components/ui/minecraft-auth-error-modal/minecraft-auth-errors'
|
||||
|
||||
export const useError = defineStore('errorsStore', {
|
||||
state: () => ({
|
||||
errorModal: null,
|
||||
@@ -15,7 +17,8 @@ export const useError = defineStore('errorsStore', {
|
||||
showError(error, context, closable = true, source = null) {
|
||||
if (
|
||||
error.message &&
|
||||
error.message.includes('Minecraft authentication error:') &&
|
||||
(error.message.includes('Minecraft authentication error:') ||
|
||||
findMinecraftAuthError(error.message)) &&
|
||||
this.minecraftAuthErrorModal
|
||||
) {
|
||||
this.minecraftAuthErrorModal.show(error)
|
||||
|
||||
@@ -71,7 +71,7 @@ export const installVersionDependencies = async (profile, version, reason, onDep
|
||||
return installed
|
||||
}
|
||||
|
||||
const queueInstall = async (projectId, resolvedVersion) => {
|
||||
const queueInstall = async (projectId, resolvedVersion, dependentOn) => {
|
||||
if (!resolvedVersion?.id) return false
|
||||
|
||||
const versionId = resolvedVersion.id
|
||||
@@ -91,7 +91,11 @@ export const installVersionDependencies = async (profile, version, reason, onDep
|
||||
if (resolvedProjectId) {
|
||||
queuedProjectVersions.set(resolvedProjectId, versionId)
|
||||
}
|
||||
queuedInstalls.push({ versionId, projectId: resolvedProjectId })
|
||||
queuedInstalls.push({
|
||||
versionId,
|
||||
projectId: resolvedProjectId,
|
||||
dependentOnVersionId: dependentOn?.id,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -159,7 +163,7 @@ export const installVersionDependencies = async (profile, version, reason, onDep
|
||||
if (!resolved) continue
|
||||
|
||||
const { depVersion, depProjectId } = resolved
|
||||
const queued = await queueInstall(depProjectId, depVersion)
|
||||
const queued = await queueInstall(depProjectId, depVersion, inputVersion)
|
||||
if (queued && depProjectId) {
|
||||
await announceDependency(depProjectId, depVersion)
|
||||
}
|
||||
@@ -176,8 +180,8 @@ export const installVersionDependencies = async (profile, version, reason, onDep
|
||||
for (let i = 0; i < queuedInstalls.length; i += batchSize) {
|
||||
const batch = queuedInstalls.slice(i, i + batchSize)
|
||||
await Promise.all(
|
||||
batch.map(async ({ versionId }) => {
|
||||
await add_project_from_version(profile.path, versionId, reason)
|
||||
batch.map(async ({ versionId, dependentOnVersionId }) => {
|
||||
await add_project_from_version(profile.path, versionId, reason, dependentOnVersionId)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
let systemThemeMq: MediaQueryList | null = null
|
||||
|
||||
export const DEFAULT_FEATURE_FLAGS = {
|
||||
project_background: false,
|
||||
page_path: false,
|
||||
@@ -53,21 +55,22 @@ export const useTheming = defineStore('themeStore', {
|
||||
this.setThemeClass()
|
||||
},
|
||||
setThemeClass() {
|
||||
const html = document.getElementsByTagName('html')[0]
|
||||
for (const theme of THEME_OPTIONS) {
|
||||
document.getElementsByTagName('html')[0].classList.remove(`${theme}-mode`)
|
||||
html.classList.remove(`${theme}-mode`)
|
||||
}
|
||||
|
||||
systemThemeMq?.removeEventListener('change', this.setThemeClass)
|
||||
systemThemeMq = null
|
||||
|
||||
let theme = this.selectedTheme
|
||||
if (this.selectedTheme === 'system') {
|
||||
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
if (darkThemeMq.matches) {
|
||||
theme = 'dark'
|
||||
} else {
|
||||
theme = 'light'
|
||||
}
|
||||
systemThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
systemThemeMq.addEventListener('change', this.setThemeClass)
|
||||
theme = systemThemeMq.matches ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
document.getElementsByTagName('html')[0].classList.add(`${theme}-mode`)
|
||||
html.classList.add(`${theme}-mode`)
|
||||
},
|
||||
getFeatureFlag(key: FeatureFlag) {
|
||||
return this.featureFlags[key] ?? DEFAULT_FEATURE_FLAGS[key]
|
||||
|
||||
@@ -117,6 +117,7 @@ fn main() {
|
||||
"equip_skin",
|
||||
"remove_custom_skin",
|
||||
"save_custom_skin",
|
||||
"set_custom_skin_order",
|
||||
"unequip_skin",
|
||||
"flush_pending_skin_change",
|
||||
"flush_pending_skin_change_for_profile",
|
||||
|
||||
@@ -14,6 +14,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
equip_skin,
|
||||
remove_custom_skin,
|
||||
save_custom_skin,
|
||||
set_custom_skin_order,
|
||||
unequip_skin,
|
||||
flush_pending_skin_change,
|
||||
flush_pending_skin_change_for_profile,
|
||||
@@ -91,6 +92,14 @@ pub async fn save_custom_skin(
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|set_custom_skin_order', texture_keys)`
|
||||
///
|
||||
/// See also: [minecraft_skins::set_custom_skin_order]
|
||||
#[tauri::command]
|
||||
pub async fn set_custom_skin_order(texture_keys: Vec<String>) -> Result<()> {
|
||||
Ok(minecraft_skins::set_custom_skin_order(texture_keys).await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|unequip_skin')`
|
||||
///
|
||||
/// See also: [minecraft_skins::unequip_skin]
|
||||
|
||||
@@ -251,8 +251,15 @@ pub async fn profile_add_project_from_version(
|
||||
path: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
) -> Result<String> {
|
||||
Ok(profile::add_project_from_version(path, version_id, reason).await?)
|
||||
Ok(profile::add_project_from_version(
|
||||
path,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
// Adds a project to a profile from a path
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,189 @@
|
||||
<template>
|
||||
<div class="relative overflow-clip rounded-xl bg-bg px-4 py-3">
|
||||
<div
|
||||
class="absolute bottom-0 left-0 top-0 w-1"
|
||||
:class="
|
||||
charge.type === 'refund' ? 'bg-purple' : (chargeStatuses[charge.status]?.color ?? 'bg-blue')
|
||||
"
|
||||
/>
|
||||
<div class="grid w-full grid-cols-[1fr_auto] items-center gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span>
|
||||
<span class="font-bold text-contrast">
|
||||
<template v-if="charge.status === 'succeeded'"> Succeeded </template>
|
||||
<template v-else-if="charge.status === 'failed'"> Failed </template>
|
||||
<template v-else-if="charge.status === 'cancelled'"> Cancelled </template>
|
||||
<template v-else-if="charge.status === 'processing'"> Processing </template>
|
||||
<template v-else-if="charge.status === 'open'"> Upcoming </template>
|
||||
<template v-else-if="charge.status === 'expiring'"> Expiring </template>
|
||||
<template v-else> {{ charge.status }} </template>
|
||||
</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span>
|
||||
<template v-if="charge.type === 'refund'"> Refund </template>
|
||||
<template v-else-if="charge.type === 'subscription'">
|
||||
<template v-if="charge.status === 'cancelled'"> Subscription </template>
|
||||
<template v-else-if="isLatestCharge"> Started subscription </template>
|
||||
<template v-else> Subscription renewal </template>
|
||||
</template>
|
||||
<template v-else-if="charge.type === 'one-time'"> One-time charge </template>
|
||||
<template v-else-if="charge.type === 'proration'"> Proration charge </template>
|
||||
<template v-else> {{ charge.status }} </template>
|
||||
</span>
|
||||
<template v-if="charge.status !== 'cancelled'">
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="productMetadata && productMetadata.type === 'pyro'"
|
||||
class="flex items-center gap-1 text-sm text-secondary"
|
||||
>
|
||||
<span class="font-bold">Product:</span>
|
||||
<span v-if="productMetadata.ram">{{ productMetadata.ram / 1024 }}GB RAM</span>
|
||||
<span v-else>Unknown RAM</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span v-if="productMetadata.cpu">{{ productMetadata.cpu }} vCPU</span>
|
||||
<span v-else>Unknown CPU</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span v-if="productMetadata.storage">{{ productMetadata.storage / 1024 }}GB Storage</span>
|
||||
<span v-else>Unknown Storage</span>
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
<span v-if="productMetadata.swap">{{ productMetadata.swap }}MB Swap</span>
|
||||
<span v-else>Unknown Swap</span>
|
||||
</span>
|
||||
<span class="text-sm text-secondary">
|
||||
<span
|
||||
v-if="charge.status === 'cancelled' && dayjs(charge.due).isBefore(dayjs())"
|
||||
class="font-bold"
|
||||
>
|
||||
Ended:
|
||||
</span>
|
||||
<span v-else-if="charge.status === 'cancelled'" class="font-bold">Ends:</span>
|
||||
<span v-else-if="charge.type === 'refund'" class="font-bold">Issued:</span>
|
||||
<span v-else class="font-bold">Due:</span>
|
||||
{{ formatDateTime(charge.due) }}
|
||||
<span class="text-secondary">({{ formatRelativeTime(charge.due) }}) </span>
|
||||
</span>
|
||||
<span v-if="charge.last_attempt != null" class="text-sm text-secondary">
|
||||
<span v-if="charge.status === 'failed'" class="font-bold">Last attempt:</span>
|
||||
<span v-else class="font-bold">Charged:</span>
|
||||
{{ formatDateTime(charge.last_attempt) }}
|
||||
<span class="text-secondary">({{ formatRelativeTime(charge.last_attempt) }}) </span>
|
||||
</span>
|
||||
<div class="flex w-full items-center gap-1 text-xs text-secondary">
|
||||
{{ charge.status }}
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ charge.type }}
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
|
||||
{{ formatDateTimeShort(charge.due) }}
|
||||
<template v-if="charge.subscription_interval">
|
||||
<span class="text-secondary opacity-50">•</span>
|
||||
{{ charge.subscription_interval }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled v-if="isRefunded">
|
||||
<div class="button-like disabled"><CheckIcon /> Charge refunded</div>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else-if="charge.status === 'succeeded' && charge.type !== 'refund'"
|
||||
color="red"
|
||||
color-fill="text"
|
||||
>
|
||||
<button @click="emit('refund', charge)">
|
||||
<CurrencyIcon />
|
||||
Refund options
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else-if="charge.status === 'failed' || charge.status === 'open'"
|
||||
color="red"
|
||||
color-fill="text"
|
||||
>
|
||||
<button @click="emit('modify', charge, subscription)">
|
||||
<CurrencyIcon />
|
||||
Modify charge
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { CheckIcon, CurrencyIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, useFormatDateTime, useFormatPrice, useRelativeTime } from '@modrinth/ui'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { products } from '~/generated/state.json'
|
||||
|
||||
const props = defineProps<{
|
||||
charge: Labrinth.Billing.Internal.Charge
|
||||
subscription: Labrinth.Billing.Internal.UserSubscription
|
||||
allCharges: Labrinth.Billing.Internal.Charge[]
|
||||
chargeIndex: number
|
||||
chargeCount: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
refund: [charge: Labrinth.Billing.Internal.Charge]
|
||||
modify: [
|
||||
charge: Labrinth.Billing.Internal.Charge,
|
||||
subscription: Labrinth.Billing.Internal.UserSubscription,
|
||||
]
|
||||
}>()
|
||||
|
||||
const formatPrice = useFormatPrice()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
const formatDateTimeShort = useFormatDateTime({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
})
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
const isLatestCharge = computed(() => props.chargeIndex === props.chargeCount - 1)
|
||||
|
||||
const isRefunded = computed(() =>
|
||||
props.allCharges.some(
|
||||
(charge) => charge.type === 'refund' && charge.parent_charge_id === props.charge.id,
|
||||
),
|
||||
)
|
||||
|
||||
const productMetadata = computed(
|
||||
() =>
|
||||
products.find((product) => product.prices.some((price) => price.id === props.charge.price_id))
|
||||
?.metadata,
|
||||
)
|
||||
|
||||
const chargeStatuses = {
|
||||
open: {
|
||||
color: 'bg-blue',
|
||||
},
|
||||
processing: {
|
||||
color: 'bg-orange',
|
||||
},
|
||||
succeeded: {
|
||||
color: 'bg-green',
|
||||
},
|
||||
failed: {
|
||||
color: 'bg-red',
|
||||
},
|
||||
cancelled: {
|
||||
color: 'bg-red',
|
||||
},
|
||||
expiring: {
|
||||
color: 'bg-orange',
|
||||
},
|
||||
}
|
||||
</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'
|
||||
@@ -461,12 +463,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="visibleQuickReplies.length > 0">
|
||||
<OverflowMenu :options="visibleQuickReplies">
|
||||
Quick Reply
|
||||
Quick reply
|
||||
<ChevronDownIcon />
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user