mirror of
https://github.com/modrinth/code.git
synced 2026-08-03 06:35:53 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ec93f538a | ||
|
|
581fb85ce4 | ||
|
|
09dead50d2 | ||
|
|
772e0ee220 | ||
|
|
86b0de3cee | ||
|
|
d174d96b74 | ||
|
|
1d193ed01b | ||
|
|
adf365d99d | ||
|
|
f3f48c3c6f | ||
|
|
e072f2237b | ||
|
|
306eee3a21 | ||
|
|
ca1d66d070 | ||
|
|
7e1400d111 | ||
|
|
7595e77170 | ||
|
|
08fcc61d35 | ||
|
|
b36801c5ed | ||
|
|
4ed1a1ae7f | ||
|
|
04db01cb55 | ||
|
|
8f5185de1c | ||
|
|
9f6db31785 | ||
|
|
a869086ce9 | ||
|
|
2af6a1b36f | ||
|
|
d4381f513f | ||
|
|
a281f13f15 | ||
|
|
a9641dadff | ||
|
|
c94dde9b47 | ||
|
|
976644d1e6 | ||
|
|
11fe90a69b | ||
|
|
1108086854 | ||
|
|
5aea892a39 | ||
|
|
3f3e6f5199 | ||
|
|
2efcd383bb | ||
|
|
faec9c2965 | ||
|
|
a0e8c7f924 | ||
|
|
6efdfdf17e | ||
|
|
aec268c6e9 | ||
|
|
2c096a85d6 | ||
|
|
240e5455cc | ||
|
|
c538a9ec6d | ||
|
|
72458f5c41 | ||
|
|
82e4eb7b40 | ||
|
|
c9bfc4e9b6 | ||
|
|
1ea96df00e | ||
|
|
75c5316dc3 | ||
|
|
4ee7623837 | ||
|
|
f65479ee15 | ||
|
|
a903e46be9 | ||
|
|
4497131206 | ||
|
|
4871abfb3a | ||
|
|
b0ed808745 | ||
|
|
169224560b | ||
|
|
106edc3a51 | ||
|
|
ede405c650 | ||
|
|
1dd1629884 | ||
|
|
0070c9877b | ||
|
|
eb208eed8b | ||
|
|
c37bf75853 | ||
|
|
7838008396 | ||
|
|
454c708fd6 | ||
|
|
3ffa78aa07 | ||
|
|
7dba9cbe54 | ||
|
|
716c4e9a21 | ||
|
|
f85a2d3ec1 | ||
|
|
d055dc68dc | ||
|
|
50a87ba933 | ||
|
|
6030cb560c | ||
|
|
c498230ebf | ||
|
|
4bbc5905e4 | ||
|
|
40f5db64d8 | ||
|
|
8d72a42be5 | ||
|
|
61c8cd75cd | ||
|
|
b46f6d0141 |
@@ -1,6 +1,6 @@
|
||||
name: 👥 Bug with Modrinth Servers
|
||||
description: For issues with a Modrinth Servers product.
|
||||
labels: [servers]
|
||||
name: 👥 Bug with Modrinth Hosting
|
||||
description: For issues with a Modrinth Hosting product.
|
||||
labels: [hosting]
|
||||
type: 'bug'
|
||||
body:
|
||||
- type: checkboxes
|
||||
@@ -19,7 +19,7 @@ body:
|
||||
options:
|
||||
- Modrinth App
|
||||
- Modrinth.com website
|
||||
- Modrinth Servers
|
||||
- Modrinth Hosting
|
||||
- Modrinth API for developers
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
@@ -13,35 +13,53 @@ on:
|
||||
- '**/wrangler.jsonc'
|
||||
- '**/pnpm-*.yaml'
|
||||
- '.github/workflows/frontend-deploy.yml'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
environment:
|
||||
required: true
|
||||
type: string
|
||||
description: 'The environment to deploy to (staging-preview or production-preview)'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.repository_owner == 'modrinth'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure environment
|
||||
id: meta
|
||||
run: |
|
||||
echo "cmd=deploy" >> $GITHUB_OUTPUT
|
||||
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
|
||||
|
||||
ENV_INPUT="${{ inputs.environment }}"
|
||||
REF="${{ github.ref }}"
|
||||
SHA_SHORT="${GITHUB_SHA::8}"
|
||||
|
||||
if [ "$ENV_INPUT" = "staging-preview" ]; then
|
||||
echo "env=staging" >> $GITHUB_OUTPUT
|
||||
echo "url=https://git-$SHA_SHORT-frontend-staging.modrinth.workers.dev" >> $GITHUB_OUTPUT
|
||||
echo "cmd=versions upload --preview-alias git-$SHA_SHORT --var PREVIEW:true" >> $GITHUB_OUTPUT
|
||||
|
||||
elif [ "$ENV_INPUT" = "production-preview" ]; then
|
||||
echo "env=production" >> $GITHUB_OUTPUT
|
||||
echo "url=https://git-$SHA_SHORT-frontend.modrinth.workers.dev" >> $GITHUB_OUTPUT
|
||||
echo "cmd=versions upload --preview-alias git-$SHA_SHORT --var PREVIEW:true" >> $GITHUB_OUTPUT
|
||||
|
||||
elif [ "$REF" = "refs/heads/main" ]; then
|
||||
echo "env=staging" >> $GITHUB_OUTPUT
|
||||
echo "url=https://staging.modrinth.com" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.ref }}" != "refs/heads/prod" ] && [ "${{ github.ref }}" != "refs/heads/main" ]; then
|
||||
echo "env=staging" >> $GITHUB_OUTPUT
|
||||
echo "url=https://git-${GITHUB_SHA::8}-frontend-staging.modrinth.workers.dev" >> $GITHUB_OUTPUT
|
||||
echo "cmd=versions upload --preview-alias git-${GITHUB_SHA::8} --var PREVIEW:true" >> $GITHUB_OUTPUT
|
||||
|
||||
else
|
||||
# Production env should be empty
|
||||
# Production env (no preview)
|
||||
echo "env=production" >> $GITHUB_OUTPUT
|
||||
echo "url=https://modrinth.com" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
@@ -76,6 +94,20 @@ jobs:
|
||||
CF_PAGES_BRANCH: ${{ github.ref_name }}
|
||||
CF_PAGES_COMMIT_SHA: ${{ github.sha }}
|
||||
CF_PAGES_URL: ${{ steps.meta.outputs.url }}
|
||||
BUILD_ENV: ${{ steps.meta.outputs.env }}
|
||||
PREVIEW: ${{ inputs.environment != '' && 'true' || 'false' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Create Sentry release and upload sourcemaps
|
||||
uses: getsentry/action-release@v3
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: modrinth
|
||||
SENTRY_PROJECT: knossos-server
|
||||
with:
|
||||
environment: ${{ steps.meta.outputs.env }}
|
||||
sourcemaps: ./apps/frontend/.output/server
|
||||
url_prefix: '~/'
|
||||
|
||||
- name: Deploy Cloudflare Worker
|
||||
id: wrangler
|
||||
@@ -83,7 +115,7 @@ jobs:
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
environment: ${{ steps.meta.outputs.env }}
|
||||
environment: ${{ steps.meta.outputs.env != 'production' && steps.meta.outputs.env || '' }}
|
||||
workingDirectory: ./apps/frontend
|
||||
packageManager: pnpm
|
||||
wranglerVersion: '4.54.0'
|
||||
@@ -97,3 +129,15 @@ jobs:
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"hosts": ["modrinth.com", "www.modrinth.com", "staging.modrinth.com"]}' \
|
||||
https://api.cloudflare.com/client/v4/zones/e39df17b9c4ef44cbce2646346ee6d33/purge_cache
|
||||
|
||||
- name: Write deployment URL to file
|
||||
if: ${{ inputs.environment != '' }}
|
||||
run: |
|
||||
echo "${{ steps.meta.outputs.url }}" > deployment-url-${{ inputs.environment }}.txt
|
||||
|
||||
- name: Upload deployment URL
|
||||
if: ${{ inputs.environment != '' }}
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: deployment-url-${{ inputs.environment }}
|
||||
path: deployment-url-${{ inputs.environment }}.txt
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Deploy frontend preview
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'apps/frontend/**/*'
|
||||
- 'packages/ui/**/*'
|
||||
- 'packages/utils/**/*'
|
||||
- 'packages/assets/**/*'
|
||||
- '**/wrangler.jsonc'
|
||||
- '**/pnpm-*.yaml'
|
||||
- '.github/workflows/frontend-preview.yml'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.repository_owner == 'modrinth' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ./.github/workflows/frontend-deploy.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
matrix:
|
||||
environment: [staging-preview, production-preview]
|
||||
with:
|
||||
environment: ${{ matrix.environment }}
|
||||
|
||||
comment:
|
||||
if: github.repository_owner == 'modrinth' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
steps:
|
||||
- name: Download deployment URLs
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: deployment-url-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Read deployment URLs
|
||||
id: urls
|
||||
run: |
|
||||
STAGING_PREVIEW_URL=$(cat deployment-url-staging-preview.txt)
|
||||
PRODUCTION_PREVIEW_URL=$(cat deployment-url-production-preview.txt)
|
||||
|
||||
echo "Production preview URL: $PRODUCTION_PREVIEW_URL"
|
||||
echo "Staging preview URL: $STAGING_PREVIEW_URL"
|
||||
|
||||
echo "staging-preview-url=$STAGING_PREVIEW_URL" >> $GITHUB_OUTPUT
|
||||
echo "production-preview-url=$PRODUCTION_PREVIEW_URL" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/find-comment@v3
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Frontend previews
|
||||
|
||||
- name: Comment deploy URL on PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/create-or-update-comment@v5
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
body: |
|
||||
## Frontend previews
|
||||
|
||||
Last deployed commit is [${{ github.sha }}](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.sha }})
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| staging | ${{ steps.urls.outputs.staging-preview-url }} |
|
||||
| production | ${{ steps.urls.outputs.production-preview-url }} |
|
||||
edit-mode: replace
|
||||
@@ -73,3 +73,6 @@ apps/labrinth/fixtures/demo
|
||||
storybook-static
|
||||
|
||||
.wrangler/
|
||||
|
||||
# frontend robots.txt
|
||||
apps/frontend/src/public/robots.txt
|
||||
|
||||
Generated
+5
@@ -12,6 +12,11 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/app-lib/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/ariadne/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/path-util/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-log/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-util/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/muralpay/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
Vendored
+6
-6
@@ -11,21 +11,21 @@
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "always"
|
||||
},
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
|
||||
Generated
+2
-14
@@ -4523,6 +4523,7 @@ dependencies = [
|
||||
"const_format",
|
||||
"dashmap",
|
||||
"deadpool-redis",
|
||||
"derive_more 2.0.1",
|
||||
"dotenv-build",
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -4558,7 +4559,6 @@ dependencies = [
|
||||
"rustls 0.23.32",
|
||||
"rusty-money",
|
||||
"sentry",
|
||||
"sentry-actix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
@@ -5061,6 +5061,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"eyre",
|
||||
"sentry",
|
||||
"tracing",
|
||||
"tracing-ecs",
|
||||
"tracing-subscriber",
|
||||
@@ -7846,19 +7847,6 @@ dependencies = [
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-actix"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc694e6ffc8d5d7fdb2a33923b0358f6ad41c0b428ced034b349b9e2b08260bc"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-web",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-backtrace"
|
||||
version = "0.45.0"
|
||||
|
||||
+1
-2
@@ -150,7 +150,6 @@ sentry = { version = "0.45.0", default-features = false, features = [
|
||||
"reqwest",
|
||||
"rustls",
|
||||
] }
|
||||
sentry-actix = "0.45.0"
|
||||
serde = "1.0.228"
|
||||
serde_bytes = "0.11.19"
|
||||
serde_cbor = "0.11.2"
|
||||
@@ -239,7 +238,7 @@ manual_assert = "warn"
|
||||
manual_instant_elapsed = "warn"
|
||||
manual_is_variant_and = "warn"
|
||||
manual_let_else = "warn"
|
||||
map_unwrap_or = "warn"
|
||||
map_unwrap_or = "allow"
|
||||
match_bool = "warn"
|
||||
needless_collect = "warn"
|
||||
negative_feature_names = "warn"
|
||||
|
||||
@@ -159,20 +159,21 @@ const reset_icon = () => {
|
||||
const createInstance = async () => {
|
||||
creatingInstance.value = true
|
||||
|
||||
const loader =
|
||||
versions.value[0].loaders[0] !== 'forge' &&
|
||||
versions.value[0].loaders[0] !== 'fabric' &&
|
||||
versions.value[0].loaders[0] !== 'quilt'
|
||||
? 'vanilla'
|
||||
: versions.value[0].loaders[0]
|
||||
const gameVersions = versions.value[0].game_versions
|
||||
const gameVersion = gameVersions[0]
|
||||
|
||||
const id = await create(
|
||||
name.value,
|
||||
versions.value[0].game_versions[0],
|
||||
loader,
|
||||
'latest',
|
||||
icon.value,
|
||||
).catch(handleError)
|
||||
const loaders = versions.value[0].loaders
|
||||
const loader = loaders.contains('fabric')
|
||||
? 'fabric'
|
||||
: loaders.contains('neoforge')
|
||||
? 'neoforge'
|
||||
: loaders.contains('forge')
|
||||
? 'forge'
|
||||
: loaders.contains('quilt')
|
||||
? 'quilt'
|
||||
: 'vanilla'
|
||||
|
||||
const id = await create(name.value, gameVersion, loader, 'latest', icon.value).catch(handleError)
|
||||
|
||||
await installMod(id, versions.value[0].id).catch(handleError)
|
||||
|
||||
|
||||
@@ -77,6 +77,9 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "Přidat přítele"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural, one {žádost} few {žádosti} other {žádostí}} o přátelství"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Poslat žádost o přátelství"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecraft autentificeringsservere kan måske være nede lige nu. Tjek din internet forbindelse og prøv igen senere."
|
||||
"message": "Minecraft authentication servere kan måske være nede lige nu. Tjek din internet forbindelse og prøv igen senere."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Kan ikke nå autentificeringsservere"
|
||||
@@ -23,6 +23,9 @@
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "Javainstallationer"
|
||||
},
|
||||
"app.settings.tabs.language": {
|
||||
"message": "Sprog"
|
||||
},
|
||||
"app.settings.tabs.privacy": {
|
||||
"message": "Privatliv"
|
||||
},
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"message": "Update wird heruntergeladen ({percent}%)"
|
||||
},
|
||||
"app.update.reload-to-update": {
|
||||
"message": "Neu laden um Update zu installieren"
|
||||
"message": "Neu laden, um das Update zu installieren"
|
||||
},
|
||||
"friends.action.add-friend": {
|
||||
"message": "Freund hinzufügen"
|
||||
@@ -105,7 +105,7 @@
|
||||
"message": "Freund entfernen"
|
||||
},
|
||||
"friends.friend.request-sent": {
|
||||
"message": "Freundesanfrage gesendet"
|
||||
"message": "Freundschaftsanfrage gesendet"
|
||||
},
|
||||
"friends.friend.view-profile": {
|
||||
"message": "Profil anzeigen"
|
||||
@@ -270,7 +270,7 @@
|
||||
"message": "Wird vor dem Starten der Instanz ausgeführt."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.enter": {
|
||||
"message": "Vor Spielstart auszuführender Befehl eingeben..."
|
||||
"message": "Vor Spielstart auszuführenden Befehl eingeben..."
|
||||
},
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Start-Hooks"
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"message": "Servidor de Minecraft"
|
||||
},
|
||||
"instance.server-modal.resource-pack": {
|
||||
"message": "Resource pack"
|
||||
"message": "Paquete de recursos"
|
||||
},
|
||||
"instance.settings.tabs.general": {
|
||||
"message": "General"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Los servidores de autenticación de Minecraft podrían estar inactivos. Comprueba tu conexión a internet e inténtalo más tarde."
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modo desarrollador activado."
|
||||
},
|
||||
@@ -18,7 +21,7 @@
|
||||
"message": "Instalaciones de Java"
|
||||
},
|
||||
"app.settings.tabs.language": {
|
||||
"message": "Lenguaje"
|
||||
"message": "Idioma"
|
||||
},
|
||||
"app.settings.tabs.privacy": {
|
||||
"message": "Privacidad"
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "Java asennukset"
|
||||
},
|
||||
"app.settings.tabs.language": {
|
||||
"message": "Kieli"
|
||||
},
|
||||
"app.settings.tabs.privacy": {
|
||||
"message": "Yksityisyys"
|
||||
},
|
||||
@@ -48,7 +51,7 @@
|
||||
"message": "Ladataan..."
|
||||
},
|
||||
"app.update-toast.reload": {
|
||||
"message": "Lataa uudelleen"
|
||||
"message": "Uudelleen lataa"
|
||||
},
|
||||
"app.update-toast.title": {
|
||||
"message": "Päivitys saatavilla"
|
||||
@@ -78,13 +81,13 @@
|
||||
"message": "{count} ystävä{count, plural, one {pyyntö} other {pyyntöä}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Lähetä ystäväpyyntö"
|
||||
"message": "Lähetä kaveripyyntö"
|
||||
},
|
||||
"friends.add-friend.title": {
|
||||
"message": "Lisätään ystävää"
|
||||
"message": "Lisätään kaveria"
|
||||
},
|
||||
"friends.add-friend.username.description": {
|
||||
"message": "Se voi olla eri kuin heidän Minecraft-käyttäjänimensä!"
|
||||
"message": "Se ei voi olla eri kuin heidän Minecraft-käyttäjänimi!"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "Syötä Modrinth-käyttäjätunnus..."
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
"message": "Gestion des ressources"
|
||||
},
|
||||
"app.update-toast.body": {
|
||||
"message": "L'application Modrinth v{version} est prête à être installée ! Relancez l'application pour faire la mise à jour maintenant, ou automatiquement à la fermeture de l'application Modrinth."
|
||||
"message": "Modrinth App v{version} est prête à être installée ! Relancez l'application pour faire la mise à jour maintenant, ou automatiquement à la fermeture de Modrinth App."
|
||||
},
|
||||
"app.update-toast.body.download-complete": {
|
||||
"message": "Le téléchargement de l'application Modrinth v{version} est terminé ! Relancez l'application pour faire la mise à jour maintenant, ou automatiquement à la fermeture de l'application Modrinth."
|
||||
"message": "Le téléchargement de Modrinth App v{version} est terminé ! Relancez l'application pour mettre à jour maintenant, ou automatiquement à la fermeture de Modrinth App."
|
||||
},
|
||||
"app.update-toast.body.metered": {
|
||||
"message": "L'application Modrinth v{version} est disponible dès maintenant ! Lorsque vous êtes sur un réseau limité ou en données mobiles, nous ne téléchargerons pas les mises à jour automatiquement."
|
||||
"message": "Modrinth App v{version} est disponible dès maintenant ! Lorsque vous êtes sur un réseau limité ou en données mobiles, nous ne téléchargerons pas les mises à jour automatiquement."
|
||||
},
|
||||
"app.update-toast.changelog": {
|
||||
"message": "Journal des modifications"
|
||||
@@ -78,7 +78,7 @@
|
||||
"message": "Ajouter un ami"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} {count, plural,one {demande}other {demandes}} d'ami"
|
||||
"message": "{count} {count, plural, one {demande} other {demandes}} d'ami"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Envoyer une demande d'ami"
|
||||
@@ -126,7 +126,7 @@
|
||||
"message": "En attente"
|
||||
},
|
||||
"friends.no-friends-match": {
|
||||
"message": "Aucuns amis ne correspondent à \"{query}\""
|
||||
"message": "Aucuns amis ne correspondent à « {query} »"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "Chercher des amis..."
|
||||
@@ -249,7 +249,7 @@
|
||||
"message": "Crochets de lancement"
|
||||
},
|
||||
"instance.settings.tabs.hooks.custom-hooks": {
|
||||
"message": "Crochets de lancement custom"
|
||||
"message": "Crochets de lancement personnalisés"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "Les crochets permettent aux usagers avancés d'exécuter certaines commandes systèmes avant et après le lancement du jeu."
|
||||
@@ -258,7 +258,7 @@
|
||||
"message": "Post-fermeture"
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.description": {
|
||||
"message": "Exécuté après fermeture."
|
||||
"message": "Exécuté après la fermeture du jeu."
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.enter": {
|
||||
"message": "Entrer commande de post-fermeture..."
|
||||
@@ -369,7 +369,7 @@
|
||||
"message": "Réparation"
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.confirm.description": {
|
||||
"message": "La réparation réinstalle les dépendances de Minecraft et vérifie la corruption. Cela peut résoudre les problèmes de lancement du jeu en raison d'erreurs liées au lanceur, mais ne résoudra pas les problèmes ou plantages liés aux mods installés."
|
||||
"message": "La réparation réinstalle les dépendances de Minecraft et vérifie la corruption. Cela peut résoudre les problèmes de lancement du jeu en raison d'erreurs liées au launcher, mais ne résoudra pas les problèmes ou plantages liés aux mods installés."
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.confirm.title": {
|
||||
"message": "Réparer l'instance ?"
|
||||
@@ -384,7 +384,7 @@
|
||||
"message": "Voir toutes les versions"
|
||||
},
|
||||
"instance.settings.tabs.installation.tooltip.action.change-version": {
|
||||
"message": "Changer la version"
|
||||
"message": "changer la version"
|
||||
},
|
||||
"instance.settings.tabs.installation.tooltip.action.install": {
|
||||
"message": "installer"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Peladen autentikasi Minecraft mungkin sedang tidak tersedia saat ini. Periksa koneksi internet Anda dan coba lagi nanti."
|
||||
"message": "Server autentikasi Minecraft mungkin sedang tidak tersedia saat ini. Periksa koneksi internet Anda dan coba lagi nanti."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Tidak dapat terhubung ke peladen autentikasi"
|
||||
"message": "Tidak dapat terhubung ke server autentikasi"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Mode pengembang dihidupkan."
|
||||
@@ -93,7 +93,7 @@
|
||||
"message": "Masukkan nama pengguna Modrinth..."
|
||||
},
|
||||
"friends.add-friend.username.title": {
|
||||
"message": "Apakah nama pengguna Modrinth teman Anda?"
|
||||
"message": "Apa nama pengguna Modrinth teman Anda?"
|
||||
},
|
||||
"friends.add-friends-to-share": {
|
||||
"message": "<link>Tambah teman</link> untuk melihat apa yang mereka mainkan!"
|
||||
@@ -141,7 +141,7 @@
|
||||
"message": "Tambah dan mainkan"
|
||||
},
|
||||
"instance.add-server.add-server": {
|
||||
"message": "Tambah peladen"
|
||||
"message": "Tambah server"
|
||||
},
|
||||
"instance.add-server.resource-pack.disabled": {
|
||||
"message": "Dimatikan"
|
||||
@@ -153,10 +153,10 @@
|
||||
"message": "Konfirmasi"
|
||||
},
|
||||
"instance.add-server.title": {
|
||||
"message": "Tambah peladen"
|
||||
"message": "Tambah server"
|
||||
},
|
||||
"instance.edit-server.title": {
|
||||
"message": "Sunting peladen"
|
||||
"message": "Sunting server"
|
||||
},
|
||||
"instance.edit-world.hide-from-home": {
|
||||
"message": "Sembunyikan dari Beranda"
|
||||
@@ -186,7 +186,7 @@
|
||||
"message": "Nama"
|
||||
},
|
||||
"instance.server-modal.placeholder-name": {
|
||||
"message": "Peladen Minecraft"
|
||||
"message": "Server Minecraft"
|
||||
},
|
||||
"instance.server-modal.resource-pack": {
|
||||
"message": "Paket sumber"
|
||||
@@ -474,10 +474,10 @@
|
||||
"message": "Pengaturan"
|
||||
},
|
||||
"instance.worlds.a_minecraft_server": {
|
||||
"message": "Peladen Minecraft"
|
||||
"message": "Server Minecraft"
|
||||
},
|
||||
"instance.worlds.cant_connect": {
|
||||
"message": "Tidak dapat menghubungkan ke peladen"
|
||||
"message": "Tidak dapat menghubungkan ke server"
|
||||
},
|
||||
"instance.worlds.copy_address": {
|
||||
"message": "Salin alamat"
|
||||
@@ -495,13 +495,13 @@
|
||||
"message": "Mode Menantang"
|
||||
},
|
||||
"instance.worlds.incompatible_server": {
|
||||
"message": "Peladen tidak cocok"
|
||||
"message": "Server tidak cocok"
|
||||
},
|
||||
"instance.worlds.no_contact": {
|
||||
"message": "Peladen tidak dapat dihubungi"
|
||||
"message": "Server tidak dapat dihubungi"
|
||||
},
|
||||
"instance.worlds.no_server_quick_play": {
|
||||
"message": "Anda hanya dapat memasuki peladen secara langsung pada Minecraft versi Alpha 1.0.5 ke atas"
|
||||
"message": "Anda hanya dapat memasuki server secara langsung pada Minecraft versi Alpha 1.0.5 ke atas"
|
||||
},
|
||||
"instance.worlds.no_singleplayer_quick_play": {
|
||||
"message": "Anda hanya dapat memasuki dunia bermain sendiri secara langsung pada Minecraft versi Alpha 1.20 ke atas"
|
||||
@@ -510,7 +510,7 @@
|
||||
"message": "Mainkan wujud"
|
||||
},
|
||||
"instance.worlds.type.server": {
|
||||
"message": "Peladen"
|
||||
"message": "Server"
|
||||
},
|
||||
"instance.worlds.type.singleplayer": {
|
||||
"message": "Bermain sendiri"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"message": "Modrinth App v{version} è ora disponibile! Poiché sei su una rete a consumo, non l''abbiamo scaricata automaticamente."
|
||||
},
|
||||
"app.update-toast.changelog": {
|
||||
"message": "Changelog"
|
||||
"message": "Novità"
|
||||
},
|
||||
"app.update-toast.download": {
|
||||
"message": "Scarica ({size})"
|
||||
@@ -60,7 +60,7 @@
|
||||
"message": "Download completato"
|
||||
},
|
||||
"app.update.complete-toast.text": {
|
||||
"message": "Clicca qui per leggere il changelog."
|
||||
"message": "Clicca qui per leggere le ultime novità in inglese."
|
||||
},
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "La versione {version} è stata installata con successo!"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecraft 인증 서버가 현재 다운되었을 수 있습니다. 인터넷 연결을 확인한 후 나중에 다시 시도하세요."
|
||||
"message": "Minecraft 인증 서버가 일시적으로 중단되었을 수 있습니다. 인터넷 연결을 확인한 후 나중에 다시 시도하세요."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "인증 서버에 연결할 수 없습니다"
|
||||
@@ -9,7 +9,7 @@
|
||||
"message": "개발자 모드가 활성화되었습니다."
|
||||
},
|
||||
"app.settings.downloading": {
|
||||
"message": "v{version} 다운로드 중"
|
||||
"message": "버전 다운로드 중: v{version}"
|
||||
},
|
||||
"app.settings.tabs.appearance": {
|
||||
"message": "모양"
|
||||
@@ -87,7 +87,7 @@
|
||||
"message": "친구 추가 중"
|
||||
},
|
||||
"friends.add-friend.username.description": {
|
||||
"message": "마인크래프트 사용자명과 다를 수 있습니다!"
|
||||
"message": "Minecraft 사용자명과 다를 수 있습니다!"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "Modrinth 사용자명 입력..."
|
||||
@@ -129,7 +129,7 @@
|
||||
"message": "\"{query}\"와 일치하는 친구가 없습니다"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "친구 검색 중..."
|
||||
"message": "친구 찾는 중..."
|
||||
},
|
||||
"friends.section.heading": {
|
||||
"message": "{title} - {count}"
|
||||
@@ -201,22 +201,22 @@
|
||||
"message": "인스턴스 삭제"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.description": {
|
||||
"message": "기기에서 인스턴스를 완전히 삭제합니다. 월드, 설정, 설치된 모든 콘텐츠가 함께 제거됩니다. 주의하세요, 한 번 삭제하면 인스턴스를 복구할 방법은 없습니다."
|
||||
"message": "기기에서 인스턴스를 완전히 삭제합니다. 세계, 설정, 설치된 모든 콘텐츠가 함께 삭제됩니다. 주의하세요, 한 번 삭제하면 인스턴스를 복구할 수 없습니다."
|
||||
},
|
||||
"instance.settings.tabs.general.deleting.button": {
|
||||
"message": "삭제중..."
|
||||
"message": "삭제 중..."
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-button": {
|
||||
"message": "복제"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-button.tooltip.installing": {
|
||||
"message": "설치중에는 복제할 수 없습니다."
|
||||
"message": "설치 중에는 복제할 수 없습니다."
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance": {
|
||||
"message": "인스턴스 복제"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance.description": {
|
||||
"message": "인스턴스의 복사본을 생성합니다. 월드, 설정, 모드, 기타 등등도 함께 복사됩니다."
|
||||
"message": "인스턴스의 복사본을 생성합니다. 세계, 설정, 모드, 기타 등등도 함께 복사됩니다."
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "아이콘 수정"
|
||||
@@ -279,7 +279,7 @@
|
||||
"message": "래퍼"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.description": {
|
||||
"message": "Minecraft를 실행을 위한 래퍼 명령어 입니다."
|
||||
"message": "Minecraft 실행을 위한 래퍼 명령어 입니다."
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.enter": {
|
||||
"message": "래퍼 명령어 입력..."
|
||||
@@ -351,7 +351,7 @@
|
||||
"message": "모드팩 재설치 중"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.confirm.description": {
|
||||
"message": "재설치하면 설치 또는 수정된 모든 콘텐츠가 모드팩에서 제공하는 콘텐츠로 초기화되며, 기존 팩에 추가한 모드나 콘텐츠는 모두 제거됩니다. 인스턴스에 변경 사항이 있는 경우 예상치 못한 동작은 해결할 수 있지만, 현재 월드가 추가로 설치된 콘텐츠에 의존하고 있다면 기존 월드가 손상될 수 있습니다."
|
||||
"message": "재설치하면 설치 또는 수정된 모든 콘텐츠가 모드팩에서 제공하는 콘텐츠로 초기화되며, 기존 팩에 추가한 모드나 콘텐츠는 모두 제거됩니다. 인스턴스에 변경 사항이 있는 경우 예상치 못한 동작은 해결할 수 있지만, 현재 세계가 추가로 설치된 콘텐츠에 의존하고 있다면 기존 세계가 손상될 수 있습니다."
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.confirm.title": {
|
||||
"message": "정말로 이 인스턴스를 다시 설치하시겠습니까?"
|
||||
@@ -519,7 +519,7 @@
|
||||
"message": "인스턴스 보기"
|
||||
},
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "사용중인 월드"
|
||||
"message": "사용 중인 세계"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "인스턴스에서 관리"
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
"message": "Ganchos de inicio personalizados"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "Ganchos permitem a utilizadores avançados executarem certos comandos de sistema antes e depois de abrir o jogo."
|
||||
"message": "Ganchos permitem a utilizadores avançados executarem certos comandos de sistema antes e após abrir o jogo."
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit": {
|
||||
"message": "Pós-saída"
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
{
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Serverele de autentificare Minecraft pot fi indisponibile în acest moment. Verificați conexiunea la internet și încercați din nou mai târziu."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Nu se pot accesa serverele de autentificare"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modul dezvoltator activat."
|
||||
},
|
||||
@@ -17,6 +23,9 @@
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "Instalări Java"
|
||||
},
|
||||
"app.settings.tabs.language": {
|
||||
"message": "Limbă"
|
||||
},
|
||||
"app.settings.tabs.privacy": {
|
||||
"message": "Confidențialitate"
|
||||
},
|
||||
@@ -65,6 +74,63 @@
|
||||
"app.update.reload-to-update": {
|
||||
"message": "Reîncarcă pentru a instala actualizarea"
|
||||
},
|
||||
"friends.action.add-friend": {
|
||||
"message": "Adaugă un prieten"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Trimite cerere de prietenie"
|
||||
},
|
||||
"friends.add-friend.title": {
|
||||
"message": "Adăugarea unui prieten"
|
||||
},
|
||||
"friends.add-friend.username.description": {
|
||||
"message": "Poate fi diferit de numele lor de utilizator Minecraft!"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "Introduceți numele de utilizator Modrinth..."
|
||||
},
|
||||
"friends.add-friend.username.title": {
|
||||
"message": "Care este numele de utilizator Modrinth al prietenului tău?"
|
||||
},
|
||||
"friends.add-friends-to-share": {
|
||||
"message": "<link>Adaugă prieteni</link> pentru a vedea ce joacă!"
|
||||
},
|
||||
"friends.friend.cancel-request": {
|
||||
"message": "Anulează cererea"
|
||||
},
|
||||
"friends.friend.remove-friend": {
|
||||
"message": "Eliminare prieten"
|
||||
},
|
||||
"friends.friend.request-sent": {
|
||||
"message": "Cerere de prietenie trimisă"
|
||||
},
|
||||
"friends.friend.view-profile": {
|
||||
"message": "Vizualizați profilul"
|
||||
},
|
||||
"friends.heading": {
|
||||
"message": "Prieteni"
|
||||
},
|
||||
"friends.heading.active": {
|
||||
"message": "Activ"
|
||||
},
|
||||
"friends.heading.offline": {
|
||||
"message": "Inactiv"
|
||||
},
|
||||
"friends.heading.online": {
|
||||
"message": "Online"
|
||||
},
|
||||
"friends.heading.pending": {
|
||||
"message": "În așteptare"
|
||||
},
|
||||
"friends.no-friends-match": {
|
||||
"message": "Nu există prieteni care să corespundă cu \"{query}”"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "Caută prieteni..."
|
||||
},
|
||||
"friends.sign-in-to-add-friends": {
|
||||
"message": "<link>Conectează-te la un cont Modrinth</link> pentru a adăuga prieteni și a vedea ce joacă!"
|
||||
},
|
||||
"instance.add-server.add-and-play": {
|
||||
"message": "Adaugă și joacă"
|
||||
},
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"message": "Друзья"
|
||||
},
|
||||
"friends.heading.active": {
|
||||
"message": "Активно"
|
||||
"message": "В игре"
|
||||
},
|
||||
"friends.heading.offline": {
|
||||
"message": "Не в сети"
|
||||
@@ -123,7 +123,7 @@
|
||||
"message": "В сети"
|
||||
},
|
||||
"friends.heading.pending": {
|
||||
"message": "В ожидании"
|
||||
"message": "В ожидании"
|
||||
},
|
||||
"friends.no-friends-match": {
|
||||
"message": "Нет друзей по запросу «{query}»"
|
||||
@@ -219,16 +219,16 @@
|
||||
"message": "Создаёт копию сборки, включая миры, настройки, моды и т. д."
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "Изменить значок"
|
||||
"message": "Изменить иконку"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.remove": {
|
||||
"message": "Удалить значок"
|
||||
"message": "Удалить иконку"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.replace": {
|
||||
"message": "Заменить значок"
|
||||
"message": "Заменить иконку"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.select": {
|
||||
"message": "Выбрать значок"
|
||||
"message": "Выбрать иконку"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups": {
|
||||
"message": "Группировка"
|
||||
@@ -420,7 +420,7 @@
|
||||
"message": "Установленное содержимое связано со сборкой на Modrinth, что блокирует возможность менять версии игры, модов или загрузчик. Нажатие на кнопку ниже необратимо разрушит эту связь."
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.title": {
|
||||
"message": "Отвязка сборки"
|
||||
"message": "Отвязка из мод-пака"
|
||||
},
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java и память"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"message": "Kaynak yönetimi"
|
||||
},
|
||||
"app.update-toast.body": {
|
||||
"message": "Modrinth App v{version} kurulmaya hazır! Şimdi güncellemek için uygulamayı yeniden yükle ya da sen Modrinth App'i kapatınca otomatik olarak güncellensin."
|
||||
"message": "Modrinth App v{version} kuruluma hazır! Şimdi güncellemek için uygulamayı yeniden yükle ya da sen Modrinth App'i kapatınca otomatik olarak güncellensin."
|
||||
},
|
||||
"app.update-toast.body.download-complete": {
|
||||
"message": "Modrinth App v{version} indirildi. Şimdi güncellemek için uygulamayı yeniden yükle ya da Modrinth App'i kapatınca otomatik olarak güncellensin."
|
||||
@@ -60,7 +60,7 @@
|
||||
"message": "Yükleme tamamlandı"
|
||||
},
|
||||
"app.update.complete-toast.text": {
|
||||
"message": "Değişiklikleri görüntülemek için buraya tıklayın."
|
||||
"message": "Değişiklik günlüğünü görüntülemek için buraya tıklayın."
|
||||
},
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "{version} sürümü başarıyla kuruldu!"
|
||||
@@ -87,7 +87,7 @@
|
||||
"message": "Bir arkadaş ekleme"
|
||||
},
|
||||
"friends.add-friend.username.description": {
|
||||
"message": "Onun Minecraft kullanıcı adından farklı olabilir!"
|
||||
"message": "Minecraft kullanıcı adlarından farklı olabilir!"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "Modrinth kullanıcı adını girin..."
|
||||
@@ -135,7 +135,7 @@
|
||||
"message": "{title} - {count}"
|
||||
},
|
||||
"friends.sign-in-to-add-friends": {
|
||||
"message": "<link>Modrinth hesabına giriş yaparak</link> arkadaş ekle ve ne oynadıklarını gör!"
|
||||
"message": "Arkadaş eklemek ve ne oynadıklarını görmek için bir <link>Modrinth hesabına giriş yap</link>!"
|
||||
},
|
||||
"instance.add-server.add-and-play": {
|
||||
"message": "Ekle ve oyna"
|
||||
@@ -144,10 +144,10 @@
|
||||
"message": "Sunucu ekle"
|
||||
},
|
||||
"instance.add-server.resource-pack.disabled": {
|
||||
"message": "Kapalı"
|
||||
"message": "Devre dışı"
|
||||
},
|
||||
"instance.add-server.resource-pack.enabled": {
|
||||
"message": "Açık"
|
||||
"message": "Etkin"
|
||||
},
|
||||
"instance.add-server.resource-pack.prompt": {
|
||||
"message": "Sor"
|
||||
@@ -159,7 +159,7 @@
|
||||
"message": "Sunucuyu düzenle"
|
||||
},
|
||||
"instance.edit-world.hide-from-home": {
|
||||
"message": "Ana Sayfadan gizle"
|
||||
"message": "Ana sayfada gizle"
|
||||
},
|
||||
"instance.edit-world.name": {
|
||||
"message": "Dünya Adı"
|
||||
@@ -408,7 +408,7 @@
|
||||
"message": "(bilinmeyen sürüm)"
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.button": {
|
||||
"message": "Kurulum bağını koparr"
|
||||
"message": "Kurulum bağlantısını kes"
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.confirm.description": {
|
||||
"message": "Devam edersen bu kurulumu geri bağlayamazsın. Mod paketi güncellemeleri almazsın ve bu kurulum normal kuruluma dönüşür."
|
||||
@@ -474,7 +474,7 @@
|
||||
"message": "Ayarlar"
|
||||
},
|
||||
"instance.worlds.a_minecraft_server": {
|
||||
"message": "Bir Minecraft Sunucusu"
|
||||
"message": "Minecraft Sunucusu"
|
||||
},
|
||||
"instance.worlds.cant_connect": {
|
||||
"message": "Sunucuya bağlanılamıyor"
|
||||
@@ -483,7 +483,7 @@
|
||||
"message": "Adresi kopyala"
|
||||
},
|
||||
"instance.worlds.dont_show_on_home": {
|
||||
"message": "Ana Sayfada gösterme"
|
||||
"message": "Ana sayfada gösterme"
|
||||
},
|
||||
"instance.worlds.filter.available": {
|
||||
"message": "Kullanılabilir"
|
||||
@@ -531,6 +531,6 @@
|
||||
"message": "Yükleyici veren kurulumlar"
|
||||
},
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Kurulum ile senkronize et"
|
||||
"message": "Kurulumla senkronize et"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Сервери автентифікації Minecraft можливо зараз не працюють. Перевірте з’єднання з Інтернетом та спробуйте пізніше."
|
||||
"message": "Сервери автентифікації Minecraft можуть зараз не працювати. Перевірте з’єднання з Інтернетом та спробуйте пізніше."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Не вдається зв’язатися зі серверами автентифікації"
|
||||
@@ -9,13 +9,13 @@
|
||||
"message": "Увімкнено режим розробника."
|
||||
},
|
||||
"app.settings.downloading": {
|
||||
"message": "Завантаження v{version}"
|
||||
"message": "Завантаження версії {version}"
|
||||
},
|
||||
"app.settings.tabs.appearance": {
|
||||
"message": "Вигляд"
|
||||
},
|
||||
"app.settings.tabs.default-instance-options": {
|
||||
"message": "Налаштування усталеного профілю"
|
||||
"message": "Усталені налашт. профілю"
|
||||
},
|
||||
"app.settings.tabs.feature-flags": {
|
||||
"message": "Спеціальні функції"
|
||||
@@ -33,13 +33,13 @@
|
||||
"message": "Керування ресурсами"
|
||||
},
|
||||
"app.update-toast.body": {
|
||||
"message": "Modrinth App v{version} готова до встановлення! Перезапустіть, щоб оновити зараз. Або, оновлення буде здійснено автоматично, коли закриєте Modrinth App."
|
||||
"message": "Версія {version} Modrinth App готова до встановлення! Перезапустіть, щоб оновити зараз, або дозвольте оновитися автоматично, коли закриватимете Modrinth App."
|
||||
},
|
||||
"app.update-toast.body.download-complete": {
|
||||
"message": "Modrinth App v{version} уже завантажилась. Перезапустіть, щоб оновити зараз. Або, оновлення буде здійснено автоматично, коли закриєте Modrinth App."
|
||||
"message": "Версія {version} Modrinth App уже завантажилася. Перезапустіть, щоб оновити зараз, або дозвольте оновитися автоматично, коли закриватимете Modrinth App."
|
||||
},
|
||||
"app.update-toast.body.metered": {
|
||||
"message": "Modrinth App v{version} доступна зараз! Оскільки ви на лімітному з’єднанні, ми не завантажили її автоматично."
|
||||
"message": "Версія {version} Modrinth App доступна для завантаження! Оскільки ви на лімітному з’єднанні, ми не завантажили її автоматично."
|
||||
},
|
||||
"app.update-toast.changelog": {
|
||||
"message": "Журнал змін"
|
||||
@@ -54,13 +54,13 @@
|
||||
"message": "Перезапустити"
|
||||
},
|
||||
"app.update-toast.title": {
|
||||
"message": "Оновлення доступне"
|
||||
"message": "Доступне оновлення"
|
||||
},
|
||||
"app.update-toast.title.download-complete": {
|
||||
"message": "Завантаження завершено"
|
||||
},
|
||||
"app.update.complete-toast.text": {
|
||||
"message": "Натисніть тут, щоб побачити список змін."
|
||||
"message": "Натисніть тут, щоб побачити журнал змін."
|
||||
},
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "Версію {version} успішно встановлено!"
|
||||
@@ -72,7 +72,7 @@
|
||||
"message": "Завантаження оновлення ({percent}%)"
|
||||
},
|
||||
"app.update.reload-to-update": {
|
||||
"message": "Перезавантажте, щоб установити оновлення"
|
||||
"message": "Перезавантажте, аби встановити оновлення"
|
||||
},
|
||||
"friends.action.add-friend": {
|
||||
"message": "Додати друга"
|
||||
@@ -90,13 +90,13 @@
|
||||
"message": "Може відрізнятися від його або її імені користувача Minecraft!"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "Уведіть ім’я користувача Modrinth…"
|
||||
"message": "Уведіть ім’я користувача…"
|
||||
},
|
||||
"friends.add-friend.username.title": {
|
||||
"message": "Яке ім’я користувача Modrinth у вашого друга?"
|
||||
},
|
||||
"friends.add-friends-to-share": {
|
||||
"message": "<link>Додайте друзів</link>, щоб бачити, у що вони грають!"
|
||||
"message": "<link>Додайте друзів</link>, аби бачити, у що вони грають!"
|
||||
},
|
||||
"friends.friend.cancel-request": {
|
||||
"message": "Скасувати запит"
|
||||
@@ -126,7 +126,7 @@
|
||||
"message": "Очікується"
|
||||
},
|
||||
"friends.no-friends-match": {
|
||||
"message": "Немає друзів, котрі збігаються з «{query}»"
|
||||
"message": "Немає друзів, які збігаються з «{query}»"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "Шукати друзів…"
|
||||
@@ -156,7 +156,7 @@
|
||||
"message": "Додати сервер"
|
||||
},
|
||||
"instance.edit-server.title": {
|
||||
"message": "Змінити сервер"
|
||||
"message": "Редагувати сервер"
|
||||
},
|
||||
"instance.edit-world.hide-from-home": {
|
||||
"message": "Не показувати на головній сторінці"
|
||||
@@ -177,7 +177,7 @@
|
||||
"message": "Вимкнені проєкти"
|
||||
},
|
||||
"instance.filter.updates-available": {
|
||||
"message": "Наявні оновлення"
|
||||
"message": "Оновлення доступні"
|
||||
},
|
||||
"instance.server-modal.address": {
|
||||
"message": "Адреса"
|
||||
@@ -195,13 +195,13 @@
|
||||
"message": "Загальні"
|
||||
},
|
||||
"instance.settings.tabs.general.delete": {
|
||||
"message": "Видалити примірник"
|
||||
"message": "Видалити профіль"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.button": {
|
||||
"message": "Видалити профіль"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.description": {
|
||||
"message": "Назавжди видаляє профіль з вашого пристрою, включно з вашими світами, налаштуваннями та всім встановленим контентом.\nБудьте обережні, після видалення, відновити профіль буде неможливо."
|
||||
"message": "Назавжди видаляє профіль з вашого пристрою, включно з вашими світами, налаштуваннями та всім установленим умістом. Будьте обережні, після видалення, відновити профіль буде неможливо."
|
||||
},
|
||||
"instance.settings.tabs.general.deleting.button": {
|
||||
"message": "Видалення…"
|
||||
@@ -210,13 +210,13 @@
|
||||
"message": "Клонувати"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-button.tooltip.installing": {
|
||||
"message": "Неможливо клонувати під час встановлення."
|
||||
"message": "Неможливо клонувати під час установлення."
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance": {
|
||||
"message": "Клонувати профіль"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance.description": {
|
||||
"message": "Створює копію інсталяції, включно зі світами, налаштуваннями, модами тощо."
|
||||
"message": "Створює копію профілю, включно зі світами, налаштуваннями, модами тощо."
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "Редагувати значок"
|
||||
@@ -228,7 +228,7 @@
|
||||
"message": "Замінити значок"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.select": {
|
||||
"message": "Обрати значок"
|
||||
"message": "Вибрати значок"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups": {
|
||||
"message": "Групи бібліотеки"
|
||||
@@ -270,13 +270,13 @@
|
||||
"message": "Запускається перед відкриттям профілю."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.enter": {
|
||||
"message": "Уведіть команду, що виконується перед запуском..."
|
||||
"message": "Уведіть команду, що виконуватиметься перед запуском…"
|
||||
},
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Гуки запуску гри"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Обгортач"
|
||||
"message": "Під час гри"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.description": {
|
||||
"message": "Обгортальна команда для запуску Minecraft."
|
||||
@@ -285,7 +285,7 @@
|
||||
"message": "Уведіть обгортальну команду…"
|
||||
},
|
||||
"instance.settings.tabs.installation": {
|
||||
"message": "Інсталяція"
|
||||
"message": "Установлення"
|
||||
},
|
||||
"instance.settings.tabs.installation.change-version.already-installed.modded": {
|
||||
"message": "{platform} {version} для Minecraft {game_version} вже встановлена"
|
||||
@@ -324,7 +324,7 @@
|
||||
"message": "Установити"
|
||||
},
|
||||
"instance.settings.tabs.installation.install.in-progress": {
|
||||
"message": "Відбувається встановлення"
|
||||
"message": "Установлення триває"
|
||||
},
|
||||
"instance.settings.tabs.installation.loader-version": {
|
||||
"message": "Версія {loader}"
|
||||
@@ -339,7 +339,7 @@
|
||||
"message": "{loader} недоступний для Minecraft {version}. Спробуйте інший завантажувач модів."
|
||||
},
|
||||
"instance.settings.tabs.installation.no-modpack-found": {
|
||||
"message": "Цей екземпляр пов’язана з пакетом модів, проте сам пакет модів не вдалося знайти на Modrinth."
|
||||
"message": "Цей профіль пов’язаний зі збіркою, проте саму збірку не вдалося знайти на Modrinth."
|
||||
},
|
||||
"instance.settings.tabs.installation.platform": {
|
||||
"message": "Платформа"
|
||||
@@ -351,13 +351,13 @@
|
||||
"message": "Перевстановлення збірки"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.confirm.description": {
|
||||
"message": "Перевстановлення скине увесь доданий або модифікований уміст до наданого пакета модів, прибираючи будь-які, додатково встановлені вами моди чи вміст.\nЯкщо ви змінювали екземпляр, це може виправити непередбачувану поведінку, однак, якщо ваші світи залежать від додаткового вмісту, це може зламати ці світи."
|
||||
"message": "Перевстановлення скине ввесь доданий або модифікований уміст до наданого збіркою, прибираючи будь-які додатково встановлені вами моди чи вміст. Якщо ви змінювали профіль, це може виправити непередбачувану поведінку, однак, якщо ваші світи залежать від додаткового вмісту, світи можуть пошкодитися."
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.confirm.title": {
|
||||
"message": "Ви впевнені, що хочете перевстановити цей профіль?"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.description": {
|
||||
"message": "Скидає вміст інсталяції до початкового стану, прибираючи будь-які моди чи вміст, що було додано поверх оригінального пакета модів."
|
||||
"message": "Скидає вміст профілю до початкового стану, прибираючи будь-які моди чи вміст, що було додано поверх оригінальної збірки."
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.title": {
|
||||
"message": "Перевстановити збірку"
|
||||
@@ -369,13 +369,13 @@
|
||||
"message": "Лагодження"
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.confirm.description": {
|
||||
"message": "Відновлення перевстановлює залежності Minecraft та перевіряє їх на наявність пошкоджень. Це може розв’язати проблеми, якщо ваша гра не запускається через помилки запускача, але не розв’яже проблеми або збої, пов’язані зі встановленими модифікаціями."
|
||||
"message": "Відновлення перевстановлює залежності Minecraft та перевіряє їх на наявність пошкоджень. Це може розв’язати проблеми, якщо ваша гра не запускається через помилки запускача, але не розв’яже проблеми або збої, пов’язані зі встановленими модами."
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.confirm.title": {
|
||||
"message": "Відновити профіль?"
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.in-progress": {
|
||||
"message": "Лагодження в процесі"
|
||||
"message": "Лагодження триває"
|
||||
},
|
||||
"instance.settings.tabs.installation.reset-selections": {
|
||||
"message": "Скинути до поточного"
|
||||
@@ -399,7 +399,7 @@
|
||||
"message": "Неможливо {action} під час установлення"
|
||||
},
|
||||
"instance.settings.tabs.installation.tooltip.cannot-while-offline": {
|
||||
"message": "Неможливо {action} без з’єднання з Інтернетом"
|
||||
"message": "Неможливо {action} без з’єднання з інтернетом"
|
||||
},
|
||||
"instance.settings.tabs.installation.tooltip.cannot-while-repairing": {
|
||||
"message": "Неможливо {action} під час лагодження"
|
||||
@@ -411,13 +411,13 @@
|
||||
"message": "Відв’язати профіль"
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.confirm.description": {
|
||||
"message": "Якщо ви продовжите, ви не зможете повторно підв’язати його без створення повністю нової інсталяції. Ви більше не будете отримувати оновлення пакета модів та вона стане звичайною."
|
||||
"message": "Якщо ви продовжите, ви не зможете повторно підв’язати його без створення повністю нового профілю. Ви більше не будете отримувати оновлення збірки та цей профіль стане звичайним."
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.confirm.title": {
|
||||
"message": "Ви впевнені, що хочете відв’язати цей екземпляр?"
|
||||
"message": "Ви впевнені, що хочете відв’язати цей профіль?"
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.description": {
|
||||
"message": "Цей профіль пов’язаний з пакетом модів, тобто, не можна оновлювати моди та не можна змінювати завантажувач чи версію Minecraft. Відв’язання призведе до остаточного від’єднання цієї інсталяції від пакета модів."
|
||||
"message": "Цей профіль пов’язаний зі збіркою, тобто, не можна оновлювати моди та не можна змінювати завантажувач чи версію Minecraft. Відв’язання призведе до остаточного від’єднання цього профілю від збірки."
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.title": {
|
||||
"message": "Відв’язати від збірки"
|
||||
@@ -435,7 +435,7 @@
|
||||
"message": "Аргументи Java"
|
||||
},
|
||||
"instance.settings.tabs.java.java-installation": {
|
||||
"message": "Установлення Java"
|
||||
"message": "Інсталяції Java"
|
||||
},
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "Виділена пам’ять"
|
||||
@@ -450,7 +450,7 @@
|
||||
"message": "Повний екран"
|
||||
},
|
||||
"instance.settings.tabs.window.fullscreen.description": {
|
||||
"message": "Змусити гру запускатися на повний екран (використовуючи options.txt)."
|
||||
"message": "Запускати гру в повноекранному режимі (за допомогою options.txt)."
|
||||
},
|
||||
"instance.settings.tabs.window.height": {
|
||||
"message": "Висота"
|
||||
@@ -483,7 +483,7 @@
|
||||
"message": "Скопіювати адресу"
|
||||
},
|
||||
"instance.worlds.dont_show_on_home": {
|
||||
"message": "Не показувати на Головній"
|
||||
"message": "Не показувати на головній"
|
||||
},
|
||||
"instance.worlds.filter.available": {
|
||||
"message": "Доступно"
|
||||
@@ -495,10 +495,10 @@
|
||||
"message": "Режим гардкору"
|
||||
},
|
||||
"instance.worlds.incompatible_server": {
|
||||
"message": "Несумісний сервер"
|
||||
"message": "Сервер несумісний"
|
||||
},
|
||||
"instance.worlds.no_contact": {
|
||||
"message": "Не вдалося зв’язатися з сервером"
|
||||
"message": "Не вдалося зв’язатися зі сервером"
|
||||
},
|
||||
"instance.worlds.no_server_quick_play": {
|
||||
"message": "Запускатися у сервери напряму можливо лише починаючи з версії Minecraft Alpha 1.0.5"
|
||||
@@ -519,7 +519,7 @@
|
||||
"message": "Переглянути профіль"
|
||||
},
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Світ зараз використовується"
|
||||
"message": "Світ наразі використовується"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Надано профілем"
|
||||
@@ -528,7 +528,7 @@
|
||||
"message": "Версія гри надана профілем"
|
||||
},
|
||||
"search.filter.locked.instance-loader.title": {
|
||||
"message": "Запускач наданий профілем"
|
||||
"message": "Завантажувач наданий профілем"
|
||||
},
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Синхронізувати з профілем"
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"message": "Thêm một người bạn"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "{count} yêu cầu kết bạn"
|
||||
"message": "{count} yêu cầu kết bạn {count, plural, one {request} other {requests}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "Gửi yêu cầu kết bạn"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"message": "Modrinth App v{version} 更新已就绪!立即重启更新,或退出时自动安装。"
|
||||
},
|
||||
"app.update-toast.body.download-complete": {
|
||||
"message": "Modrinth App v{version} 下载完成。立即重启更新,或退出时自动安装。"
|
||||
"message": "Modrinth App v{version} 已经完成下载。现在重新启动以更新,或在关闭 Modrinth App 时自动更新。"
|
||||
},
|
||||
"app.update-toast.body.metered": {
|
||||
"message": "Modrinth App v{version} 现已发布!由于你正在使用按流量计费的网络,该更新未自动下载。"
|
||||
@@ -63,7 +63,7 @@
|
||||
"message": "点击此处查看更新日志。"
|
||||
},
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "此版本{version} 已成功安装!"
|
||||
"message": "版本 {version} 已成功安装!"
|
||||
},
|
||||
"app.update.download-update": {
|
||||
"message": "下载更新"
|
||||
@@ -96,7 +96,7 @@
|
||||
"message": "好友的 Modrinth 用户名是什么?"
|
||||
},
|
||||
"friends.add-friends-to-share": {
|
||||
"message": "<link>添加好友</link>,看看他们在玩什么!"
|
||||
"message": "<link>添加好友</link> 来看看他们都在玩什么!"
|
||||
},
|
||||
"friends.friend.cancel-request": {
|
||||
"message": "取消请求"
|
||||
@@ -129,7 +129,7 @@
|
||||
"message": "没有对应名字“{query}”的好友"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "搜索好友…"
|
||||
"message": "搜索好友……"
|
||||
},
|
||||
"friends.section.heading": {
|
||||
"message": "{title} - {count}"
|
||||
@@ -165,7 +165,7 @@
|
||||
"message": "名称"
|
||||
},
|
||||
"instance.edit-world.placeholder-name": {
|
||||
"message": "Minecraft 存档"
|
||||
"message": "Minecraft 世界"
|
||||
},
|
||||
"instance.edit-world.reset-icon": {
|
||||
"message": "重置图标"
|
||||
@@ -195,10 +195,10 @@
|
||||
"message": "通用"
|
||||
},
|
||||
"instance.settings.tabs.general.delete": {
|
||||
"message": "删除此实例"
|
||||
"message": "删除实例"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.button": {
|
||||
"message": "删除此实例"
|
||||
"message": "删除实例"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.description": {
|
||||
"message": "将从设备中永久删除该实例,包括所有存档、配置文件及已安装内容。此操作不可撤销,请谨慎操作。"
|
||||
@@ -246,13 +246,13 @@
|
||||
"message": "名称"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "启动钩子"
|
||||
"message": "启动Hooks"
|
||||
},
|
||||
"instance.settings.tabs.hooks.custom-hooks": {
|
||||
"message": "自定义启动钩子"
|
||||
"message": "自定义启动Hooks"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "钩子允许高级用户在启动游戏前后运行特定的系统命令。"
|
||||
"message": "Hooks允许高级用户在启动游戏前后运行特定的系统命令。"
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit": {
|
||||
"message": "退出后"
|
||||
@@ -276,13 +276,13 @@
|
||||
"message": "游戏启动钩子"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "包装命令"
|
||||
"message": "包装器命令"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.description": {
|
||||
"message": "用于启动 Minecraft 的包装命令。"
|
||||
"message": "用于启动 Minecraft 的包装器命令。"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.enter": {
|
||||
"message": "输入包装命令……"
|
||||
"message": "输入包装器命令……"
|
||||
},
|
||||
"instance.settings.tabs.installation": {
|
||||
"message": "安装"
|
||||
@@ -351,13 +351,13 @@
|
||||
"message": "正在重新安装整合包"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.confirm.description": {
|
||||
"message": "重新安装实例将并重置所有已安装或修改的内容,恢复到整合包的初始状态,移除在原来的安装内容上另加的所有模组或内容。该操作有可能修复因实例更改导致的异常,但若你的世界目前依赖额外安装的内容的操作,此举可能会损坏现有世界。"
|
||||
"message": "重新安装实例将重置所有已安装或修改的内容,并恢复到整合包的初始状态,移除在原来的安装内容上另外添加的的所有模组或内容。该操作有可能修复因实例更改导致的异常,但如果你的世界依赖已安装的额外内容,此举可能会损坏现有世界。"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.confirm.title": {
|
||||
"message": "你确定要重新安装该实例吗?"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.description": {
|
||||
"message": "重置实例至原始状态,移除你在原有整合包上添加的所有模组及其数据。"
|
||||
"message": "重置实例至原始状态,移除你在原始整合包上添加的所有模组及内容。"
|
||||
},
|
||||
"instance.settings.tabs.installation.reinstall.title": {
|
||||
"message": "重新安装整合包"
|
||||
@@ -369,19 +369,19 @@
|
||||
"message": "修复中"
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.confirm.description": {
|
||||
"message": "修复实例时,会将重新安装 Minecraft 依赖,并检查文件完整性。该操作可能解决因启动器错误导致的启动失败问题,但不能解决由模组引发的问题和崩溃。"
|
||||
"message": "修复实例将重新安装 Minecraft 依赖,并检查文件完整性。该操作有可能解决因启动器错误导致的启动失败问题,但不能解决由模组引发的问题或崩溃。"
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.confirm.title": {
|
||||
"message": "确定修复该实例吗?"
|
||||
},
|
||||
"instance.settings.tabs.installation.repair.in-progress": {
|
||||
"message": "修复中,请闭目养神,少安毋躁"
|
||||
"message": "正在修复"
|
||||
},
|
||||
"instance.settings.tabs.installation.reset-selections": {
|
||||
"message": "重置为当前版本"
|
||||
},
|
||||
"instance.settings.tabs.installation.show-all-versions": {
|
||||
"message": "列出所有版本"
|
||||
"message": "显示所有版本"
|
||||
},
|
||||
"instance.settings.tabs.installation.tooltip.action.change-version": {
|
||||
"message": "切换版本"
|
||||
@@ -405,7 +405,7 @@
|
||||
"message": "修复时无法{action}"
|
||||
},
|
||||
"instance.settings.tabs.installation.unknown-version": {
|
||||
"message": "(嗯?未知版本)"
|
||||
"message": "(未知版本)"
|
||||
},
|
||||
"instance.settings.tabs.installation.unlink.button": {
|
||||
"message": "解除关联实例"
|
||||
@@ -438,10 +438,10 @@
|
||||
"message": "Java 安装路径"
|
||||
},
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "分配内存"
|
||||
"message": "内存分配"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "窗口"
|
||||
"message": "游戏窗口"
|
||||
},
|
||||
"instance.settings.tabs.window.custom-window-settings": {
|
||||
"message": "自定义窗口设置"
|
||||
@@ -510,7 +510,7 @@
|
||||
"message": "游玩实例"
|
||||
},
|
||||
"instance.worlds.type.server": {
|
||||
"message": "服务器"
|
||||
"message": "服务端"
|
||||
},
|
||||
"instance.worlds.type.singleplayer": {
|
||||
"message": "单人游戏"
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"message": "{title} - {count}"
|
||||
},
|
||||
"friends.sign-in-to-add-friends": {
|
||||
"message": "<link>登入 Modrinth 帳號</link>即可新增好友並查看對方正在玩什麼!"
|
||||
"message": "<link>登入 Modrinth 帳號</link>即可新增好友並查看他們正在玩什麼!"
|
||||
},
|
||||
"instance.add-server.add-and-play": {
|
||||
"message": "新增並遊玩"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from '@/App.vue'
|
||||
import i18n from '@/i18n.config'
|
||||
import i18nPlugin from '@/plugins/i18n'
|
||||
import router from '@/routes'
|
||||
|
||||
const vueScan = new VueScanPlugin({
|
||||
@@ -43,6 +43,6 @@ app.use(FloatingVue, {
|
||||
},
|
||||
},
|
||||
})
|
||||
app.use(i18n)
|
||||
app.use(i18nPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { I18N_INJECTION_KEY, type I18nContext } from '@modrinth/ui'
|
||||
import type { App } from 'vue'
|
||||
|
||||
import i18n from '@/i18n.config'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
// Install vue-i18n as before
|
||||
app.use(i18n)
|
||||
|
||||
// Wrap it in our I18nContext interface
|
||||
const context: I18nContext = {
|
||||
locale: i18n.global.locale,
|
||||
t: (key, values) => i18n.global.t(key, values ?? {}) as string,
|
||||
setLocale: (newLocale) => {
|
||||
i18n.global.locale.value = newLocale
|
||||
},
|
||||
}
|
||||
|
||||
// Provide the context at app-level
|
||||
app.provide(I18N_INJECTION_KEY, context)
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 21 KiB |
@@ -93,19 +93,24 @@ pub fn highlight_in_folder<R: Runtime>(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_path<R: Runtime>(app: tauri::AppHandle<R>, path: PathBuf) {
|
||||
if let Err(e) = app.opener().open_path(path.to_string_lossy(), None::<&str>)
|
||||
{
|
||||
tracing::error!("Failed to open path: {}", e);
|
||||
}
|
||||
pub async fn open_path<R: Runtime>(app: tauri::AppHandle<R>, path: PathBuf) {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
if let Err(e) =
|
||||
app.opener().open_path(path.to_string_lossy(), None::<&str>)
|
||||
{
|
||||
tracing::error!("Failed to open path: {}", e);
|
||||
}
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn show_launcher_logs_folder<R: Runtime>(app: tauri::AppHandle<R>) {
|
||||
pub async fn show_launcher_logs_folder<R: Runtime>(app: tauri::AppHandle<R>) {
|
||||
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
|
||||
// failure to get folder just opens filesystem
|
||||
// (ie: if in debug mode only and launcher_logs never created)
|
||||
open_path(app, path);
|
||||
open_path(app, path).await;
|
||||
}
|
||||
|
||||
// Get opening command
|
||||
|
||||
@@ -2516,6 +2516,13 @@ paths:
|
||||
description: 'Allows to filter for featured or non-featured versions only'
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: include_changelog
|
||||
required: false
|
||||
description: 'Allows you to toggle the inclusion of the changelog field in the response. It is highly recommended to use include_changelog=false in most cases unless you specifically need the changelog for all versions.'
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
responses:
|
||||
'200':
|
||||
description: Expected response to a valid request
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
**/dist
|
||||
**/.output
|
||||
**/.data
|
||||
**/.wrangler
|
||||
src/generated/**
|
||||
src/locales/**
|
||||
src/public/news/feed
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { createMessageCompiler } from '@modrinth/ui'
|
||||
|
||||
export default defineI18nConfig(() => ({
|
||||
legacy: false,
|
||||
locale: 'en-US',
|
||||
fallbackLocale: 'en-US',
|
||||
messageCompiler: createMessageCompiler(),
|
||||
missingWarn: false,
|
||||
fallbackWarn: false,
|
||||
}))
|
||||
@@ -1,17 +0,0 @@
|
||||
import { type CrowdinMessages, transformCrowdinMessages } from '@modrinth/ui'
|
||||
|
||||
// eager:false - only loads the locale requested
|
||||
const localeModules = import.meta.glob<{ default: CrowdinMessages }>(
|
||||
'../src/locales/*/index.json',
|
||||
{ eager: false },
|
||||
)
|
||||
|
||||
export default defineI18nLocale(async (locale) => {
|
||||
const loader = localeModules[`../src/locales/${locale}/index.json`]
|
||||
if (!loader) {
|
||||
console.warn(`Locale ${locale} not found`)
|
||||
return {}
|
||||
}
|
||||
const messages = await loader()
|
||||
return transformCrowdinMessages(messages.default)
|
||||
})
|
||||
@@ -1,5 +1,4 @@
|
||||
import { GenericModrinthClient, type Labrinth } from '@modrinth/api-client'
|
||||
import { LOCALES } from '@modrinth/ui/src/composables/i18n.ts'
|
||||
import serverSidedVue from '@vitejs/plugin-vue'
|
||||
import fs from 'fs/promises'
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
@@ -167,6 +166,17 @@ export default defineNuxtConfig({
|
||||
|
||||
await fs.writeFile('./src/generated/state.json', JSON.stringify(state))
|
||||
|
||||
// throw if errors and building for prod (preview & staging allowed to have errors)
|
||||
if (
|
||||
process.env.BUILD_ENV === 'production' &&
|
||||
process.env.PREVIEW !== 'true' &&
|
||||
generatedState.errors.length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`Production build failed: State generation encountered errors. Error codes: ${JSON.stringify(generatedState.errors)}; API URL: ${API_URL}`,
|
||||
)
|
||||
}
|
||||
|
||||
console.log('Tags generated!')
|
||||
|
||||
const robotsContent =
|
||||
@@ -188,6 +198,8 @@ export default defineNuxtConfig({
|
||||
pyroBaseUrl: process.env.PYRO_BASE_URL,
|
||||
siteUrl: getDomain(),
|
||||
production: isProduction(),
|
||||
buildEnv: process.env.BUILD_ENV,
|
||||
preview: process.env.PREVIEW === 'true',
|
||||
featureFlagOverrides: getFeatureFlagOverrides(),
|
||||
|
||||
owner: process.env.VERCEL_GIT_REPO_OWNER || 'modrinth',
|
||||
@@ -222,7 +234,12 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
modules: ['@nuxtjs/i18n', '@pinia/nuxt', 'floating-vue/nuxt'],
|
||||
modules: [
|
||||
'@pinia/nuxt',
|
||||
'floating-vue/nuxt',
|
||||
// Sentry causes rollup-plugin-inject errors in dev, only enable in production
|
||||
...(isProduction() ? ['@sentry/nuxt/module'] : []),
|
||||
],
|
||||
floatingVue: {
|
||||
themes: {
|
||||
'ribbit-popout': {
|
||||
@@ -237,25 +254,6 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
defaultLocale: 'en-US',
|
||||
lazy: true,
|
||||
langDir: '.',
|
||||
locales: LOCALES.map((locale) => ({
|
||||
...locale,
|
||||
file: 'locale-loader.ts',
|
||||
})),
|
||||
strategy: 'no_prefix',
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
cookieKey: 'locale',
|
||||
fallbackLocale: 'en-US',
|
||||
},
|
||||
vueI18n: './i18n.config.ts',
|
||||
bundle: {
|
||||
optimizeTranslationDirective: false,
|
||||
},
|
||||
},
|
||||
nitro: {
|
||||
rollupConfig: {
|
||||
// @ts-expect-error because of rolldown-vite - completely fine though
|
||||
@@ -266,6 +264,10 @@ export default defineNuxtConfig({
|
||||
cloudflare: {
|
||||
nodeCompat: true,
|
||||
},
|
||||
replace: {
|
||||
__SENTRY_RELEASE__: JSON.stringify(process.env.CF_PAGES_COMMIT_SHA || 'unknown'),
|
||||
__SENTRY_ENVIRONMENT__: JSON.stringify(process.env.BUILD_ENV || 'development'),
|
||||
},
|
||||
},
|
||||
devtools: {
|
||||
enabled: true,
|
||||
@@ -313,6 +315,12 @@ export default defineNuxtConfig({
|
||||
experimental: {
|
||||
asyncContext: true,
|
||||
},
|
||||
sourcemap: { client: 'hidden' },
|
||||
sentry: {
|
||||
sourcemaps: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function getApiUrl() {
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
"devDependencies": {
|
||||
"@formatjs/cli": "^6.2.12",
|
||||
"@modrinth/tooling-config": "workspace:*",
|
||||
"@nuxtjs/i18n": "^9.0.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/iso-3166-2": "^1.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.1.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"glob": "^10.2.7",
|
||||
"nuxt": "^3.20.2",
|
||||
@@ -46,12 +47,14 @@
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"@sentry/nuxt": "^10.33.0",
|
||||
"@tanstack/vue-query": "^5.90.7",
|
||||
"@types/three": "^0.172.0",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@vue-email/components": "^0.0.21",
|
||||
"@vue-email/render": "^0.0.9",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"ace-builds": "^1.36.2",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "^3.1.7",
|
||||
@@ -62,8 +65,8 @@
|
||||
"iso-3166-2": "1.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"lru-cache": "^11.2.4",
|
||||
"markdown-it": "14.1.0",
|
||||
"ace-builds": "^1.36.2",
|
||||
"pathe": "^1.1.2",
|
||||
"pinia": "^3.0.0",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { BlueskyIcon, DiscordIcon, GithubIcon, MastodonIcon, TwitterIcon } from '@modrinth/assets'
|
||||
import {
|
||||
AutoLink,
|
||||
ButtonStyled,
|
||||
defineMessage,
|
||||
defineMessages,
|
||||
@@ -15,6 +16,7 @@ import TextLogo from '~/components/brand/TextLogo.vue'
|
||||
const flags = useFeatureFlags()
|
||||
const { formatMessage } = useVIntl()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const messages = defineMessages({
|
||||
modrinthInformation: {
|
||||
@@ -302,6 +304,27 @@ function developerModeIncrement() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="flags.developerMode" class="m-0 text-sm text-secondary">
|
||||
Based on
|
||||
<a
|
||||
v-if="config.public.owner && config.public.branch"
|
||||
class="hover:underline"
|
||||
target="_blank"
|
||||
:href="`https://github.com/${config.public.owner}/code/tree/${config.public.branch}`"
|
||||
>
|
||||
{{ config.public.owner }}/{{ config.public.branch }}
|
||||
</a>
|
||||
@
|
||||
<span v-if="config.public.hash === 'unknown'">unknown</span>
|
||||
<AutoLink
|
||||
v-else
|
||||
class="text-link"
|
||||
target="_blank"
|
||||
:to="`https://github.com/${config.public.owner}/code/commit/${config.public.hash}`"
|
||||
>
|
||||
{{ config.public.hash }}
|
||||
</AutoLink>
|
||||
</p>
|
||||
<div class="flex justify-center text-center text-xs font-medium text-secondary opacity-50">
|
||||
{{ formatMessage(messages.legalDisclaimer) }}
|
||||
</div>
|
||||
|
||||
@@ -18,11 +18,18 @@
|
||||
v-if="link.icon"
|
||||
class="size-5"
|
||||
:class="{
|
||||
'text-brand': currentActiveIndex === index && !subpageSelected,
|
||||
'text-button-textSelected': currentActiveIndex === index && !subpageSelected,
|
||||
'text-secondary': currentActiveIndex !== index || subpageSelected,
|
||||
}"
|
||||
/>
|
||||
<span class="text-nowrap text-contrast">{{ link.label }}</span>
|
||||
<span
|
||||
class="text-nowrap"
|
||||
:class="{
|
||||
'text-button-textSelected': currentActiveIndex === index && !subpageSelected,
|
||||
'text-contrast': currentActiveIndex !== index || subpageSelected,
|
||||
}"
|
||||
>{{ link.label }}</span
|
||||
>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -39,14 +46,14 @@
|
||||
v-if="link.icon"
|
||||
class="size-5"
|
||||
:class="{
|
||||
'text-brand': currentActiveIndex === index && !subpageSelected,
|
||||
'text-button-textSelected': currentActiveIndex === index && !subpageSelected,
|
||||
'text-secondary': currentActiveIndex !== index || subpageSelected,
|
||||
}"
|
||||
/>
|
||||
<span
|
||||
class="text-nowrap"
|
||||
:class="{
|
||||
'text-brand': currentActiveIndex === index && !subpageSelected,
|
||||
'text-button-textSelected': currentActiveIndex === index && !subpageSelected,
|
||||
'text-contrast': currentActiveIndex !== index || subpageSelected,
|
||||
}"
|
||||
>{{ link.label }}</span
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<NewModal ref="modal">
|
||||
<template #title>
|
||||
<span class="text-lg font-extrabold text-contrast">Schedule transfer</span>
|
||||
</template>
|
||||
<div class="flex w-[550px] max-w-[90vw] flex-col gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast"> Type </span>
|
||||
<span>Select transfer type.</span>
|
||||
</label>
|
||||
<Combobox
|
||||
v-model="mode"
|
||||
:options="modeOptions"
|
||||
placeholder="Select type"
|
||||
class="max-w-[10rem]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="mode === 'servers'" class="flex flex-col gap-2">
|
||||
<label for="server-ids" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast">
|
||||
Server IDs
|
||||
<span class="text-brand-red">*</span>
|
||||
</span>
|
||||
<span>Server IDs (one per line or comma-separated.)</span>
|
||||
</label>
|
||||
<div class="textarea-wrapper">
|
||||
<textarea
|
||||
id="server-ids"
|
||||
v-model="serverIdsInput"
|
||||
rows="4"
|
||||
class="w-full bg-surface-3"
|
||||
placeholder="123e4569-e89b-12d3-a456-426614174005 123e9569-e89b-12d3-a456-413678919876"
|
||||
/>
|
||||
</div>
|
||||
<span v-if="parsedServerIds.length" class="text-sm text-secondary">
|
||||
{{ parsedServerIds.length }} server{{ parsedServerIds.length === 1 ? '' : 's' }} selected
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="node-input" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast">
|
||||
Node hostnames
|
||||
<span class="text-brand-red">*</span>
|
||||
</span>
|
||||
<span>Add nodes to transfer.</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
id="node-input"
|
||||
v-model="nodeInput"
|
||||
class="w-40"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
placeholder="us-vin200"
|
||||
@keydown.enter.prevent="addNode"
|
||||
/>
|
||||
<ButtonStyled color="blue" color-fill="text">
|
||||
<button class="shrink-0" @click="addNode">
|
||||
<PlusIcon />
|
||||
Add
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div v-if="selectedNodes.length" class="mt-1 flex flex-wrap gap-2">
|
||||
<TagItem v-for="h in selectedNodes" :key="`node-${h}`" :action="() => removeNode(h)">
|
||||
<XIcon />
|
||||
{{ h }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<label for="cordon-nodes" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast">Cordon nodes now</span>
|
||||
<span>
|
||||
Prevent new servers from being provisioned on the transferred nodes from now on.<br /><br />
|
||||
Note that if this option isn't chosen, new servers provisioned onto transferred nodes
|
||||
between now and the scheduled time will still be transferred.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle id="cordon-nodes" v-model="cordonNodes" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="tag-nodes" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast">Tag transferred nodes</span>
|
||||
<span>Optional tag to add to the transferred nodes.</span>
|
||||
</label>
|
||||
<input
|
||||
id="tag-nodes"
|
||||
v-model="tagNodes"
|
||||
class="max-w-[12rem]"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="region-select" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast"> Target region </span>
|
||||
<span>Select the destination region for transferred servers.</span>
|
||||
</label>
|
||||
<Combobox
|
||||
v-model="selectedRegion"
|
||||
:options="regions"
|
||||
placeholder="Select region"
|
||||
class="max-w-[24rem]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="tag-input" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast"> Node tags </span>
|
||||
<span>Optional preferred node tags for node selection.</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
id="tag-input"
|
||||
v-model="tagInput"
|
||||
class="w-40"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
placeholder="ovh-gen4"
|
||||
@keydown.enter.prevent="addTag"
|
||||
/>
|
||||
<ButtonStyled color="blue" color-fill="text">
|
||||
<button class="shrink-0" @click="addTag">
|
||||
<PlusIcon />
|
||||
Add
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div v-if="selectedTags.length" class="mt-1 flex flex-wrap gap-2">
|
||||
<TagItem v-for="t in selectedTags" :key="`tag-${t}`" :action="() => removeTag(t)">
|
||||
<XIcon />
|
||||
{{ t }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast"> Schedule </span>
|
||||
</label>
|
||||
<Chips
|
||||
v-model="scheduleOption"
|
||||
:items="scheduleOptions"
|
||||
:format-label="(item) => scheduleOptionLabels[item]"
|
||||
:capitalize="false"
|
||||
/>
|
||||
<input
|
||||
v-if="scheduleOption === 'later'"
|
||||
v-model="scheduledDate"
|
||||
type="datetime-local"
|
||||
class="mt-2 max-w-[16rem]"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="reason" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast">
|
||||
Reason
|
||||
<span class="text-brand-red">*</span>
|
||||
</span>
|
||||
<span>Provide a reason for this transfer batch.</span>
|
||||
</label>
|
||||
<div class="textarea-wrapper">
|
||||
<textarea
|
||||
id="reason"
|
||||
v-model="reason"
|
||||
rows="2"
|
||||
class="w-full bg-surface-3"
|
||||
placeholder="Node maintenance scheduled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="submitDisabled || submitting" @click="submit">
|
||||
<SendIcon aria-hidden="true" />
|
||||
{{ submitting ? 'Scheduling...' : 'Schedule transfer' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button @click="modal?.hide?.()">
|
||||
<XIcon aria-hidden="true" />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PlusIcon, SendIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Chips,
|
||||
Combobox,
|
||||
injectNotificationManager,
|
||||
NewModal,
|
||||
TagItem,
|
||||
Toggle,
|
||||
} from '@modrinth/ui'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
}>()
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
|
||||
const modeOptions = [
|
||||
{ value: 'servers', label: 'Servers' },
|
||||
{ value: 'nodes', label: 'Nodes' },
|
||||
]
|
||||
const mode = ref<string>('servers')
|
||||
|
||||
const serverIdsInput = ref('')
|
||||
const parsedServerIds = computed(() => {
|
||||
const input = serverIdsInput.value.trim()
|
||||
if (!input) return []
|
||||
return input
|
||||
.split(/[\n,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0)
|
||||
})
|
||||
|
||||
const nodeInput = ref('')
|
||||
const selectedNodes = ref<string[]>([])
|
||||
const cordonNodes = ref(true)
|
||||
const tagNodes = ref('')
|
||||
|
||||
type RegionOpt = { value: string; label: string }
|
||||
const regions = ref<RegionOpt[]>([])
|
||||
const selectedRegion = ref<string | null>(null)
|
||||
const nodeHostnames = ref<string[]>([])
|
||||
|
||||
const tagInput = ref('')
|
||||
const selectedTags = ref<string[]>([])
|
||||
|
||||
const scheduleOptions: ('now' | 'later')[] = ['now', 'later']
|
||||
const scheduleOptionLabels: Record<string, string> = {
|
||||
now: 'Now',
|
||||
later: 'Schedule for later',
|
||||
}
|
||||
const scheduleOption = ref<'now' | 'later'>('now')
|
||||
const scheduledDate = ref<string>('')
|
||||
|
||||
const reason = ref('')
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
function show(event?: MouseEvent) {
|
||||
void ensureOverview()
|
||||
mode.value = 'servers'
|
||||
serverIdsInput.value = ''
|
||||
selectedNodes.value = []
|
||||
cordonNodes.value = true
|
||||
tagNodes.value = `migration${dayjs().format('YYYYMMDD')}`
|
||||
selectedTags.value = []
|
||||
tagInput.value = ''
|
||||
nodeInput.value = ''
|
||||
scheduleOption.value = 'now'
|
||||
scheduledDate.value = ''
|
||||
reason.value = ''
|
||||
modal.value?.show(event)
|
||||
}
|
||||
|
||||
function hide() {
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
function addNode() {
|
||||
const v = nodeInput.value.trim()
|
||||
if (!v) return
|
||||
if (!nodeHostnames.value.includes(v)) {
|
||||
addNotification({
|
||||
title: 'Unknown node',
|
||||
text: "This hostname doesn't exist",
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!selectedNodes.value.includes(v)) selectedNodes.value.push(v)
|
||||
nodeInput.value = ''
|
||||
}
|
||||
|
||||
function removeNode(v: string) {
|
||||
selectedNodes.value = selectedNodes.value.filter((x) => x !== v)
|
||||
}
|
||||
|
||||
function addTag() {
|
||||
const v = tagInput.value.trim()
|
||||
if (!v) return
|
||||
if (!selectedTags.value.includes(v)) selectedTags.value.push(v)
|
||||
tagInput.value = ''
|
||||
}
|
||||
|
||||
function removeTag(v: string) {
|
||||
selectedTags.value = selectedTags.value.filter((x) => x !== v)
|
||||
}
|
||||
|
||||
const submitDisabled = computed(() => {
|
||||
if (!reason.value.trim()) return true
|
||||
if (mode.value === 'servers') {
|
||||
if (parsedServerIds.value.length === 0) return true
|
||||
} else {
|
||||
if (selectedNodes.value.length === 0) return true
|
||||
}
|
||||
if (scheduleOption.value === 'later' && !scheduledDate.value) return true
|
||||
return false
|
||||
})
|
||||
|
||||
async function ensureOverview() {
|
||||
if (regions.value.length || nodeHostnames.value.length) return
|
||||
try {
|
||||
const data = await useServersFetch<any>('/nodes/overview', { version: 'internal' })
|
||||
regions.value = (data.regions || []).map((r: any) => ({
|
||||
value: r.key,
|
||||
label: `${r.display_name} (${r.key})`,
|
||||
}))
|
||||
nodeHostnames.value = data.node_hostnames || []
|
||||
if (!selectedRegion.value && regions.value.length) {
|
||||
selectedRegion.value = regions.value[0].value
|
||||
}
|
||||
} catch (err) {
|
||||
addNotification({ title: 'Failed to load nodes overview', text: String(err), type: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (submitDisabled.value || submitting.value) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const scheduledAt =
|
||||
scheduleOption.value === 'now' ? undefined : dayjs(scheduledDate.value).toISOString()
|
||||
|
||||
if (mode.value === 'servers') {
|
||||
await useServersFetch('/transfers/schedule/servers', {
|
||||
version: 'internal',
|
||||
method: 'POST',
|
||||
body: {
|
||||
server_ids: parsedServerIds.value,
|
||||
scheduled_at: scheduledAt,
|
||||
target_region: selectedRegion.value || undefined,
|
||||
node_tags: selectedTags.value.length > 0 ? selectedTags.value : undefined,
|
||||
reason: reason.value.trim(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await useServersFetch('/transfers/schedule/nodes', {
|
||||
version: 'internal',
|
||||
method: 'POST',
|
||||
body: {
|
||||
node_hostnames: selectedNodes.value.slice(),
|
||||
scheduled_at: scheduledAt,
|
||||
target_region: selectedRegion.value || undefined,
|
||||
node_tags: selectedTags.value.length > 0 ? selectedTags.value : undefined,
|
||||
reason: reason.value.trim(),
|
||||
cordon_nodes: cordonNodes.value,
|
||||
tag_nodes: tagNodes.value.trim() || undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
addNotification({ title: 'Transfer scheduled', type: 'success' })
|
||||
emit('success')
|
||||
modal.value?.hide()
|
||||
} catch (err: any) {
|
||||
addNotification({
|
||||
title: 'Error scheduling transfer',
|
||||
text: err?.data?.description ?? err?.message ?? String(err),
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
hide,
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
IntlFormatted,
|
||||
normalizeChildren,
|
||||
PagewideBanner,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const flags = useFeatureFlags()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'layout.banner.preview.title',
|
||||
defaultMessage: `This is a preview deploy of the Modrinth website.`,
|
||||
},
|
||||
description: {
|
||||
id: 'layout.banner.preview.description',
|
||||
defaultMessage: `If you meant to access the official Modrinth website, visit <link>https://modrinth.com</link>. This preview deploy is used by Modrinth staff for testing purposes. It was built using <branch-link>{owner}/{branch}</branch-link> @ {commit}.`,
|
||||
},
|
||||
})
|
||||
|
||||
function hidePreviewBanner() {
|
||||
flags.value.hidePreviewBanner = true
|
||||
saveFeatureFlags()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PagewideBanner v-if="!flags.hidePreviewBanner" variant="info">
|
||||
<template #title>
|
||||
<span>{{ formatMessage(messages.title) }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<span>
|
||||
<IntlFormatted
|
||||
:message-id="messages.description"
|
||||
:values="{
|
||||
owner: config.public.owner,
|
||||
branch: config.public.branch,
|
||||
}"
|
||||
>
|
||||
<template #link="{ children }">
|
||||
<a href="https://modrinth.com" target="_blank" rel="noopener" class="text-link">
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</a>
|
||||
</template>
|
||||
<template #branch-link="{ children }">
|
||||
<a
|
||||
:href="`https://github.com/${config.public.owner}/code/tree/${config.public.branch}`"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="hover:underline"
|
||||
>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</a>
|
||||
</template>
|
||||
<template #commit>
|
||||
<span v-if="config.public.hash === 'unknown'">unknown</span>
|
||||
<a
|
||||
v-else
|
||||
:href="`https://github.com/${config.public.owner}/code/commit/${config.public.hash}`"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="text-link"
|
||||
>
|
||||
{{ config.public.hash }}
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</span>
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<ButtonStyled type="transparent" circular>
|
||||
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hidePreviewBanner">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
</template>
|
||||
+31
-4
@@ -1,15 +1,28 @@
|
||||
<template>
|
||||
<MultiStageModal ref="modal" :stages="ctx.stageConfigs" :context="ctx" />
|
||||
<MultiStageModal
|
||||
ref="modal"
|
||||
:stages="ctx.stageConfigs"
|
||||
:context="ctx"
|
||||
:breadcrumbs="!editingVersion"
|
||||
@hide="() => (modalOpen = false)"
|
||||
/>
|
||||
<DropArea
|
||||
v-if="!modalOpen"
|
||||
:accept="acceptFileFromProjectType(projectV2.project_type)"
|
||||
@change="handleDropArea"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
DropArea,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
MultiStageModal,
|
||||
} from '@modrinth/ui'
|
||||
import { acceptFileFromProjectType } from '@modrinth/utils'
|
||||
import type { ComponentExposed } from 'vue-component-type-helpers'
|
||||
|
||||
import {
|
||||
@@ -17,12 +30,17 @@ import {
|
||||
provideManageVersionContext,
|
||||
} from '~/providers/version/manage-version-modal'
|
||||
|
||||
const modal = useTemplateRef<ComponentExposed<typeof MultiStageModal>>('modal')
|
||||
const emit = defineEmits<{
|
||||
(e: 'save'): void
|
||||
}>()
|
||||
|
||||
const ctx = createManageVersionContext(modal)
|
||||
const modal = useTemplateRef<ComponentExposed<typeof MultiStageModal>>('modal')
|
||||
const modalOpen = ref(false)
|
||||
|
||||
const ctx = createManageVersionContext(modal, () => emit('save'))
|
||||
provideManageVersionContext(ctx)
|
||||
|
||||
const { newDraftVersion } = ctx
|
||||
const { newDraftVersion, editingVersion, handleNewFiles } = ctx
|
||||
|
||||
const { projectV2 } = injectProjectPageContext()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
@@ -64,6 +82,15 @@ function openCreateVersionModal(
|
||||
newDraftVersion(projectV2.value.id, version)
|
||||
modal.value?.setStage(stageId ?? 0)
|
||||
modal.value?.show()
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
async function handleDropArea(files: FileList) {
|
||||
newDraftVersion(projectV2.value.id, null)
|
||||
modal.value?.setStage(0)
|
||||
await handleNewFiles(Array.from(files))
|
||||
modal.value?.show()
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
||||
+8
-6
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 rounded-xl bg-button-bg px-4 py-1 text-button-text"
|
||||
class="flex h-11 items-center justify-between gap-2 rounded-xl bg-button-bg px-4 py-1 text-button-text"
|
||||
>
|
||||
<div class="grid max-w-[75%] grid-cols-[auto_1fr_auto] items-center gap-2">
|
||||
<Avatar v-if="icon" :src="icon" alt="dependency-icon" size="20px" :no-shadow="true" />
|
||||
@@ -9,7 +9,7 @@
|
||||
{{ name || 'Unknown Project' }}
|
||||
</span>
|
||||
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5">
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5 capitalize">
|
||||
{{ dependencyType }}
|
||||
</TagItem>
|
||||
</div>
|
||||
@@ -17,14 +17,15 @@
|
||||
<span
|
||||
v-if="versionName"
|
||||
v-tooltip="versionName"
|
||||
class="max-w-[35%] truncate whitespace-nowrap font-medium"
|
||||
class="truncate whitespace-nowrap font-medium"
|
||||
:class="!hideRemove ? 'max-w-[35%]' : 'max-w-[50%]'"
|
||||
>
|
||||
{{ versionName }}
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<div v-if="!hideRemove" class="flex items-center justify-end gap-1">
|
||||
<ButtonStyled size="standard" :circular="true">
|
||||
<button aria-label="Remove file" class="!shadow-none" @click="emitRemove">
|
||||
<button aria-label="Remove file" class="-mr-2 !shadow-none" @click="emitRemove">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -42,12 +43,13 @@ const emit = defineEmits<{
|
||||
(e: 'remove'): void
|
||||
}>()
|
||||
|
||||
const { projectId, name, icon, dependencyType, versionName } = defineProps<{
|
||||
const { projectId, name, icon, dependencyType, versionName, hideRemove } = defineProps<{
|
||||
projectId: string
|
||||
name?: string
|
||||
icon?: string
|
||||
dependencyType: Labrinth.Versions.v2.DependencyType
|
||||
versionName?: string
|
||||
hideRemove?: boolean
|
||||
}>()
|
||||
|
||||
function emitRemove() {
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div v-if="addedDependencies.length" class="5 flex flex-col gap-2">
|
||||
<template v-for="(dependency, index) in addedDependencies">
|
||||
<AddedDependencyRow
|
||||
v-if="dependency"
|
||||
:key="index"
|
||||
:project-id="dependency.projectId"
|
||||
:name="dependency.name"
|
||||
:icon="dependency.icon"
|
||||
:dependency-type="dependency.dependencyType"
|
||||
:version-name="dependency.versionName"
|
||||
:hide-remove="disableRemove"
|
||||
@remove="() => removeDependency(index)"
|
||||
/>
|
||||
</template>
|
||||
<span v-if="!addedDependencies.length"> No dependencies added. </span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
import AddedDependencyRow from './AddedDependencyRow.vue'
|
||||
|
||||
const { disableRemove } = defineProps<{
|
||||
disableRemove?: boolean
|
||||
}>()
|
||||
|
||||
const { draftVersion, dependencyProjects, dependencyVersions, projectsFetchLoading } =
|
||||
injectManageVersionContext()
|
||||
|
||||
const addedDependencies = computed(() =>
|
||||
(draftVersion.value.dependencies || [])
|
||||
.map((dep) => {
|
||||
if (!dep.project_id) return null
|
||||
|
||||
const dependencyProject = dependencyProjects.value[dep.project_id]
|
||||
const versionName = dependencyVersions.value[dep.version_id || '']?.name ?? ''
|
||||
|
||||
if (!dependencyProject && projectsFetchLoading.value) return null
|
||||
|
||||
return {
|
||||
projectId: dep.project_id,
|
||||
name: dependencyProject?.name,
|
||||
icon: dependencyProject?.icon_url,
|
||||
dependencyType: dep.dependency_type,
|
||||
versionName,
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
)
|
||||
|
||||
const removeDependency = (index: number) => {
|
||||
if (!draftVersion.value.dependencies) return
|
||||
draftVersion.value.dependencies.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
@@ -118,4 +118,17 @@ function groupLoaders(loaders: Labrinth.Tags.v2.Loader[]) {
|
||||
}
|
||||
|
||||
const groupedLoaders = computed(() => groupLoaders(loaders))
|
||||
|
||||
onMounted(() => {
|
||||
if (selectedLoaders.value.length === 0) return
|
||||
|
||||
// Find the first group that contains any of the selected loaders
|
||||
const groups = groupedLoaders.value
|
||||
for (const [groupName, loadersInGroup] of Object.entries(groups)) {
|
||||
if (loadersInGroup.some((loader) => selectedLoaders.value.includes(loader.name))) {
|
||||
loaderGroup.value = groupName as GroupLabels
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+33
-2
@@ -66,7 +66,7 @@ import type { Labrinth } from '@modrinth/api-client'
|
||||
import { SearchIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, Chips } from '@modrinth/ui'
|
||||
import { useMagicKeys } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
|
||||
type GameVersion = Labrinth.Tags.v2.GameVersion
|
||||
|
||||
@@ -147,9 +147,15 @@ function groupVersions(gameVersions: GameVersion[]) {
|
||||
)
|
||||
|
||||
const getGroupKey = (v: string) => v.split('.').slice(0, 2).join('.')
|
||||
|
||||
const getSnapshotGroupKey = (v: string) => {
|
||||
const cleanVersion = v.split('-')[0]
|
||||
return cleanVersion.split('.').slice(0, 2).join('.')
|
||||
}
|
||||
|
||||
const groups: Record<string, string[]> = {}
|
||||
|
||||
let currentGroupKey = getGroupKey(gameVersions.find((v) => v.major)?.version || '')
|
||||
let currentGroupKey = getSnapshotGroupKey(gameVersions.find((v) => v.major)?.version || '')
|
||||
|
||||
gameVersions.forEach((gameVersion) => {
|
||||
if (gameVersion.version_type === 'release') {
|
||||
@@ -157,6 +163,8 @@ function groupVersions(gameVersions: GameVersion[]) {
|
||||
if (!groups[currentGroupKey]) groups[currentGroupKey] = []
|
||||
groups[currentGroupKey].push(gameVersion.version)
|
||||
} else {
|
||||
if (!currentGroupKey) currentGroupKey = getSnapshotGroupKey(gameVersion.version)
|
||||
|
||||
const key = `${currentGroupKey} ${DEV_RELEASE_KEY}`
|
||||
if (!groups[key]) groups[key] = []
|
||||
groups[key].push(gameVersion.version)
|
||||
@@ -205,4 +213,27 @@ function compareGroupKeys(a: string, b: string) {
|
||||
function searchFilter(gameVersion: Labrinth.Tags.v2.GameVersion) {
|
||||
return gameVersion.version.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.modelValue.length === 0) return
|
||||
|
||||
// Open non-release tab if any non-release versions are selected
|
||||
const hasNonReleaseVersions = props.gameVersions.some(
|
||||
(v) => props.modelValue.includes(v.version) && v.version_type !== 'release',
|
||||
)
|
||||
|
||||
if (hasNonReleaseVersions) {
|
||||
versionType.value = 'all'
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
const firstSelectedVersion = allVersionsFlat.value.find((v) => props.modelValue.includes(v))
|
||||
if (firstSelectedVersion) {
|
||||
const buttons = Array.from(document.querySelectorAll('button'))
|
||||
const element = buttons.find((btn) => btn.textContent?.trim() === firstSelectedVersion)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+21
-45
@@ -1,27 +1,24 @@
|
||||
<template>
|
||||
<div v-if="visibleDependencies.length" class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Suggested dependencies</span>
|
||||
<div class="flex flex-col gap-2">
|
||||
<template v-for="(dependency, index) in visibleDependencies">
|
||||
<SuggestedDependency
|
||||
v-if="dependency"
|
||||
:key="index"
|
||||
:project-id="dependency.project_id"
|
||||
:name="dependency.name"
|
||||
:icon="dependency.icon"
|
||||
:dependency-type="dependency.dependency_type"
|
||||
:version-name="dependency.versionName"
|
||||
@on-add-suggestion="
|
||||
() =>
|
||||
handleAddSuggestion({
|
||||
dependency_type: dependency.dependency_type,
|
||||
project_id: dependency.project_id,
|
||||
version_id: dependency.version_id,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-2">
|
||||
<template v-for="(dependency, index) in visibleSuggestedDependencies">
|
||||
<SuggestedDependency
|
||||
v-if="dependency"
|
||||
:key="index"
|
||||
:project-id="dependency.project_id"
|
||||
:name="dependency.name"
|
||||
:icon="dependency.icon"
|
||||
:dependency-type="dependency.dependency_type"
|
||||
:version-name="dependency.versionName"
|
||||
@on-add-suggestion="
|
||||
() =>
|
||||
handleAddSuggestion({
|
||||
dependency_type: dependency.dependency_type,
|
||||
project_id: dependency.project_id,
|
||||
version_id: dependency.version_id,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -32,28 +29,7 @@ import { injectManageVersionContext } from '~/providers/version/manage-version-m
|
||||
|
||||
import SuggestedDependency from './SuggestedDependency.vue'
|
||||
|
||||
export interface SuggestedDependency extends Labrinth.Versions.v3.Dependency {
|
||||
icon?: string
|
||||
name?: string
|
||||
versionName?: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
suggestedDependencies: SuggestedDependency[]
|
||||
}>()
|
||||
|
||||
const { draftVersion } = injectManageVersionContext()
|
||||
|
||||
const visibleDependencies = computed<SuggestedDependency[]>(() =>
|
||||
props.suggestedDependencies
|
||||
.filter(
|
||||
(dep) =>
|
||||
!draftVersion.value.dependencies?.some(
|
||||
(d) => d.project_id === dep.project_id && d.version_id === dep.version_id,
|
||||
),
|
||||
)
|
||||
.sort((a, b) => (a.name || '').localeCompare(b.name || '')),
|
||||
)
|
||||
const { visibleSuggestedDependencies } = injectManageVersionContext()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'onAddSuggestion', dependency: Labrinth.Versions.v3.Dependency): void
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 rounded-xl bg-button-bg px-4 py-1 text-button-text"
|
||||
class="flex items-center justify-between gap-2 rounded-xl border-2 border-dashed border-surface-5 px-4 py-1 text-button-text"
|
||||
>
|
||||
<div class="grid max-w-[75%] grid-cols-[auto_1fr_auto] items-center gap-2">
|
||||
<Avatar v-if="icon" :src="icon" alt="dependency-icon" size="20px" :no-shadow="true" />
|
||||
@@ -9,7 +9,7 @@
|
||||
{{ name || 'Unknown Project' }}
|
||||
</span>
|
||||
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5">
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5 capitalize">
|
||||
{{ dependencyType }}
|
||||
</TagItem>
|
||||
</div>
|
||||
@@ -23,7 +23,7 @@
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<ButtonStyled size="standard" :circular="true">
|
||||
<ButtonStyled size="standard" :circular="true" type="transparent">
|
||||
<button aria-label="Add dependency" class="!shadow-none" @click="emitAddSuggestion">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
+30
-11
@@ -68,10 +68,16 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { ArrowLeftRightIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, Combobox, injectProjectPageContext } from '@modrinth/ui'
|
||||
import type { DropdownOption } from '@modrinth/ui/src/components/base/Combobox.vue'
|
||||
import type { ComboboxOption } from '@modrinth/ui/src/components/base/Combobox.vue'
|
||||
import { acceptFileFromProjectType } from '@modrinth/utils'
|
||||
|
||||
import {
|
||||
fileTypeLabels,
|
||||
injectManageVersionContext,
|
||||
} from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { projectV2 } = injectProjectPageContext()
|
||||
const { projectType } = injectManageVersionContext()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'setPrimaryFile', file?: File): void
|
||||
@@ -89,16 +95,29 @@ const { name, isPrimary, onRemove, initialFileType, editingVersion } = definePro
|
||||
const selectedType = ref<Labrinth.Versions.v3.FileType | 'primary'>(initialFileType || 'unknown')
|
||||
const primaryFileInput = ref<HTMLInputElement>()
|
||||
|
||||
const versionTypes = [
|
||||
!editingVersion && { class: 'text-sm', value: 'primary', label: 'Primary' },
|
||||
{ class: 'text-sm', value: 'unknown', label: 'Other' },
|
||||
{ class: 'text-sm', value: 'required-resource-pack', label: 'Required RP' },
|
||||
{ class: 'text-sm', value: 'optional-resource-pack', label: 'Optional RP' },
|
||||
{ class: 'text-sm', value: 'sources-jar', label: 'Sources JAR' },
|
||||
{ class: 'text-sm', value: 'dev-jar', label: 'Dev JAR' },
|
||||
{ class: 'text-sm', value: 'javadoc-jar', label: 'Javadoc JAR' },
|
||||
{ class: 'text-sm', value: 'signature', label: 'Signature' },
|
||||
].filter(Boolean) as DropdownOption<Labrinth.Versions.v3.FileType | 'primary'>[]
|
||||
const isDatapackProject = computed(() => projectType.value === 'datapack')
|
||||
|
||||
const versionTypes = computed(
|
||||
() =>
|
||||
[
|
||||
!editingVersion && { class: 'text-sm', value: 'primary', label: fileTypeLabels.primary },
|
||||
{ class: 'text-sm', value: 'unknown', label: fileTypeLabels.unknown },
|
||||
isDatapackProject.value && {
|
||||
class: 'text-sm',
|
||||
value: 'required-resource-pack',
|
||||
label: fileTypeLabels['required-resource-pack'],
|
||||
},
|
||||
isDatapackProject.value && {
|
||||
class: 'text-sm',
|
||||
value: 'optional-resource-pack',
|
||||
label: fileTypeLabels['optional-resource-pack'],
|
||||
},
|
||||
{ class: 'text-sm', value: 'sources-jar', label: fileTypeLabels['sources-jar'] },
|
||||
{ class: 'text-sm', value: 'dev-jar', label: fileTypeLabels['dev-jar'] },
|
||||
{ class: 'text-sm', value: 'javadoc-jar', label: fileTypeLabels['javadoc-jar'] },
|
||||
{ class: 'text-sm', value: 'signature', label: fileTypeLabels.signature },
|
||||
].filter(Boolean) as ComboboxOption<Labrinth.Versions.v3.FileType | 'primary'>[],
|
||||
)
|
||||
|
||||
function emitFileTypeChange() {
|
||||
if (selectedType.value === 'primary') emit('setPrimaryFile')
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 rounded-xl bg-button-bg px-4 py-2 text-button-text"
|
||||
>
|
||||
<div class="flex items-center gap-2 overflow-hidden">
|
||||
<FileIcon v-if="isPrimary" class="text-lg" />
|
||||
<FilePlusIcon v-else class="text-lg" />
|
||||
|
||||
<span v-tooltip="name" class="overflow-hidden text-ellipsis whitespace-nowrap font-medium">
|
||||
{{ name }}
|
||||
</span>
|
||||
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5">
|
||||
{{ isPrimary ? 'Primary' : fileTypeLabels[fileType ?? 'unknown'] }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { FileIcon, FilePlusIcon } from '@modrinth/assets'
|
||||
import { TagItem } from '@modrinth/ui'
|
||||
|
||||
import { fileTypeLabels } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { name, isPrimary, fileType } = defineProps<{
|
||||
name: string
|
||||
isPrimary?: boolean
|
||||
fileType?: Labrinth.Versions.v3.FileType | 'primary'
|
||||
}>()
|
||||
</script>
|
||||
@@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<MarkdownEditor
|
||||
v-model="draftVersion.changelog"
|
||||
:on-image-upload="onImageUpload"
|
||||
:max-height="500"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MarkdownEditor } from '@modrinth/ui'
|
||||
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { draftVersion } = injectManageVersionContext()
|
||||
|
||||
async function onImageUpload(file: File) {
|
||||
const response = await useImageUpload(file, { context: 'version' })
|
||||
return response.url
|
||||
}
|
||||
</script>
|
||||
@@ -1,272 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 sm:w-[512px]">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast">
|
||||
Version type <span class="text-red">*</span>
|
||||
</span>
|
||||
<Chips
|
||||
v-model="draftVersion.version_type"
|
||||
:items="['release', 'beta', 'alpha']"
|
||||
:never-empty="true"
|
||||
:capitalize="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast">
|
||||
Version number <span class="text-red">*</span>
|
||||
</span>
|
||||
<input
|
||||
id="version-number"
|
||||
v-model="draftVersion.version_number"
|
||||
placeholder="Enter version number, e.g. 1.2.3-alpha.1"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
maxlength="32"
|
||||
/>
|
||||
<span> The version number differentiates this specific version from others. </span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version subtitle </span>
|
||||
<input
|
||||
id="version-number"
|
||||
v-model="draftVersion.name"
|
||||
placeholder="Enter subtitle..."
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
maxlength="256"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template v-if="!noLoadersProject && (inferredVersionData?.loaders?.length || editingVersion)">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ usingDetectedLoaders ? 'Detected loaders' : 'Loaders' }}
|
||||
</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button
|
||||
v-tooltip="isModpack ? 'Modpack versions cannot be edited' : undefined"
|
||||
:disabled="isModpack"
|
||||
@click="editLoaders"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-1.5 gap-y-4 rounded-xl border border-solid border-surface-5 p-3 py-4"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<template
|
||||
v-for="loader in draftVersionLoaders.map((selectedLoader) =>
|
||||
loaders.find((loader) => selectedLoader === loader.name),
|
||||
)"
|
||||
>
|
||||
<TagItem
|
||||
v-if="loader"
|
||||
:key="`loader-${loader.name}`"
|
||||
class="border !border-solid border-surface-5 hover:no-underline"
|
||||
:style="`--_color: var(--color-platform-${loader.name})`"
|
||||
>
|
||||
<div v-html="loader.icon"></div>
|
||||
{{ formatCategory(loader.name) }}
|
||||
</TagItem>
|
||||
</template>
|
||||
|
||||
<span v-if="!draftVersion.loaders.length">No loaders selected.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="inferredVersionData?.game_versions?.length || editingVersion">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ usingDetectedVersions ? 'Detected versions' : 'Versions' }}
|
||||
</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button
|
||||
v-tooltip="isModpack ? 'Modpack versions cannot be edited' : undefined"
|
||||
:disabled="isModpack"
|
||||
@click="editVersions"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex max-h-56 flex-col gap-1.5 gap-y-4 overflow-y-auto rounded-xl border border-solid border-surface-5 p-3 py-4"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<TagItem
|
||||
v-for="version in draftVersion.game_versions"
|
||||
:key="version"
|
||||
class="border !border-solid border-surface-5 hover:no-underline"
|
||||
>
|
||||
{{ version }}
|
||||
</TagItem>
|
||||
|
||||
<span v-if="!draftVersion.game_versions.length">No versions selected.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="
|
||||
!noEnvironmentProject &&
|
||||
((!editingVersion && inferredVersionData?.environment) ||
|
||||
(editingVersion && draftVersion.environment))
|
||||
"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Environment </span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editEnvironment">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5 gap-y-4 rounded-xl bg-surface-2 p-3 py-4">
|
||||
<div v-if="draftVersion.environment" class="flex flex-col gap-1">
|
||||
<div class="font-semibold text-contrast">
|
||||
{{ environmentCopy.title }}
|
||||
</div>
|
||||
<div class="text-sm font-medium">{{ environmentCopy.description }}</div>
|
||||
</div>
|
||||
|
||||
<span v-else class="text-sm font-medium">No environment has been set.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { EditIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Chips,
|
||||
defineMessages,
|
||||
ENVIRONMENTS_COPY,
|
||||
TagItem,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
|
||||
import { useGeneratedState } from '~/composables/generated'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const {
|
||||
draftVersion,
|
||||
inferredVersionData,
|
||||
projectType,
|
||||
editingVersion,
|
||||
noLoadersProject,
|
||||
noEnvironmentProject,
|
||||
modal,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const generatedState = useGeneratedState()
|
||||
const loaders = computed(() => generatedState.value.loaders)
|
||||
const isModpack = computed(() => projectType.value === 'modpack')
|
||||
|
||||
const draftVersionLoaders = computed(() =>
|
||||
[
|
||||
...new Set([...draftVersion.value.loaders, ...(draftVersion.value.mrpack_loaders ?? [])]),
|
||||
].filter((loader) => loader !== 'mrpack'),
|
||||
)
|
||||
|
||||
const editLoaders = () => {
|
||||
modal.value?.setStage('from-details-loaders')
|
||||
}
|
||||
const editVersions = () => {
|
||||
modal.value?.setStage('from-details-mc-versions')
|
||||
}
|
||||
const editEnvironment = () => {
|
||||
modal.value?.setStage('from-details-environment')
|
||||
}
|
||||
|
||||
const usingDetectedVersions = computed(() => {
|
||||
if (!inferredVersionData.value?.game_versions) return false
|
||||
|
||||
const versionsMatch =
|
||||
draftVersion.value.game_versions.length === inferredVersionData.value.game_versions.length &&
|
||||
draftVersion.value.game_versions.every((version) =>
|
||||
inferredVersionData.value?.game_versions?.includes(version),
|
||||
)
|
||||
|
||||
return versionsMatch
|
||||
})
|
||||
|
||||
const usingDetectedLoaders = computed(() => {
|
||||
if (!inferredVersionData.value?.loaders) return false
|
||||
|
||||
const loadersMatch =
|
||||
draftVersion.value.loaders.length === inferredVersionData.value.loaders.length &&
|
||||
draftVersion.value.loaders.every((loader) =>
|
||||
inferredVersionData.value?.loaders?.includes(loader),
|
||||
)
|
||||
|
||||
return loadersMatch
|
||||
})
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const noEnvironmentMessage = defineMessages({
|
||||
title: {
|
||||
id: 'version.environment.none.title',
|
||||
defaultMessage: 'No environment set',
|
||||
},
|
||||
description: {
|
||||
id: 'version.environment.none.description',
|
||||
defaultMessage: 'The environment for this version has not been specified.',
|
||||
},
|
||||
})
|
||||
|
||||
const unknownEnvironmentMessage = defineMessages({
|
||||
title: {
|
||||
id: 'version.environment.unknown.title',
|
||||
defaultMessage: 'Unknown environment',
|
||||
},
|
||||
description: {
|
||||
id: 'version.environment.unknown.description',
|
||||
defaultMessage: 'The environment: "{environment}" is not recognized.',
|
||||
},
|
||||
})
|
||||
|
||||
const environmentCopy = computed(() => {
|
||||
if (!draftVersion.value.environment) {
|
||||
return {
|
||||
title: formatMessage(noEnvironmentMessage.title),
|
||||
description: formatMessage(noEnvironmentMessage.description),
|
||||
}
|
||||
}
|
||||
|
||||
const envCopy = ENVIRONMENTS_COPY[draftVersion.value.environment]
|
||||
if (envCopy) {
|
||||
return {
|
||||
title: formatMessage(envCopy.title),
|
||||
description: formatMessage(envCopy.description),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatMessage(unknownEnvironmentMessage.title),
|
||||
description: formatMessage(unknownEnvironmentMessage.description, {
|
||||
environment: draftVersion.value.environment,
|
||||
}),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,11 +1,15 @@
|
||||
<template>
|
||||
<div class="flex w-full flex-col gap-4 sm:w-[512px]">
|
||||
<template v-if="!(filesToAdd.length || draftVersion.existing_files?.length)">
|
||||
<div class="flex w-full flex-col gap-4">
|
||||
<template
|
||||
v-if="handlingNewFiles || !(filesToAdd.length || draftVersion.existing_files?.length)"
|
||||
>
|
||||
<DropzoneFileInput
|
||||
aria-label="Upload file"
|
||||
multiple
|
||||
:accept="acceptFileFromProjectType(projectV2.project_type)"
|
||||
:max-size="524288000"
|
||||
primary-prompt="Upload primary and supporting files"
|
||||
secondary-prompt="Drag and drop files or click to browse"
|
||||
@change="handleNewFiles"
|
||||
/>
|
||||
</template>
|
||||
@@ -21,11 +25,7 @@
|
||||
:is-primary="true"
|
||||
:editing-version="editingVersion"
|
||||
:on-remove="undefined"
|
||||
@set-primary-file="
|
||||
(file) => {
|
||||
if (file && !editingVersion) filesToAdd[0] = { file }
|
||||
}
|
||||
"
|
||||
@set-primary-file="(file) => file && replacePrimaryFile(file)"
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
@@ -72,7 +72,7 @@
|
||||
:editing-version="editingVersion"
|
||||
:on-remove="() => handleRemoveFile(idx + (primaryFile?.existing ? 0 : 1))"
|
||||
@set-file-type="(type) => (versionFile.fileType = type)"
|
||||
@set-primary-file="handleSetPrimaryFile(idx + (primaryFile?.existing ? 0 : 1))"
|
||||
@set-primary-file="() => swapPrimaryFile(idx + (primaryFile?.existing ? 0 : 1))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +86,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
Admonition,
|
||||
defineMessages,
|
||||
@@ -107,68 +106,18 @@ const {
|
||||
draftVersion,
|
||||
filesToAdd,
|
||||
existingFilesToDelete,
|
||||
setPrimaryFile,
|
||||
setInferredVersionData,
|
||||
handlingNewFiles,
|
||||
swapPrimaryFile,
|
||||
replacePrimaryFile,
|
||||
editingVersion,
|
||||
primaryFile,
|
||||
handleNewFiles,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const addDetectedData = async () => {
|
||||
if (editingVersion.value) return
|
||||
|
||||
const primaryFile = filesToAdd.value[0]?.file
|
||||
if (!primaryFile) return
|
||||
|
||||
try {
|
||||
const inferredData = await setInferredVersionData(primaryFile, projectV2.value)
|
||||
const mappedInferredData: Partial<Labrinth.Versions.v3.DraftVersion> = {
|
||||
...inferredData,
|
||||
name: inferredData.name || '',
|
||||
}
|
||||
|
||||
draftVersion.value = {
|
||||
...draftVersion.value,
|
||||
...mappedInferredData,
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error parsing version file data', err)
|
||||
}
|
||||
}
|
||||
|
||||
// add detected data when the primary file changes
|
||||
watch(
|
||||
() => filesToAdd.value[0]?.file,
|
||||
() => addDetectedData(),
|
||||
)
|
||||
|
||||
function handleNewFiles(newFiles: File[]) {
|
||||
// detect primary file if no primary file is set
|
||||
const primaryFileIndex = primaryFile.value ? null : detectPrimaryFileIndex(newFiles)
|
||||
|
||||
newFiles.forEach((file) => filesToAdd.value.push({ file }))
|
||||
|
||||
if (primaryFileIndex !== null) {
|
||||
if (primaryFileIndex) setPrimaryFile(primaryFileIndex)
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemoveFile(index: number) {
|
||||
filesToAdd.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function detectPrimaryFileIndex(files: File[]): number {
|
||||
const extensionPriority = ['.jar', '.zip', '.litemod', '.mrpack', '.mrpack-primary']
|
||||
|
||||
for (const ext of extensionPriority) {
|
||||
const matches = files.filter((file) => file.name.toLowerCase().endsWith(ext))
|
||||
if (matches.length > 0) {
|
||||
const shortest = matches.reduce((a, b) => (a.name.length < b.name.length ? a : b))
|
||||
return files.indexOf(shortest)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function handleRemoveExistingFile(sha1: string) {
|
||||
existingFilesToDelete.value.push(sha1)
|
||||
draftVersion.value.existing_files = draftVersion.value.existing_files?.filter(
|
||||
@@ -176,38 +125,6 @@ function handleRemoveExistingFile(sha1: string) {
|
||||
)
|
||||
}
|
||||
|
||||
function handleSetPrimaryFile(index: number) {
|
||||
setPrimaryFile(index)
|
||||
}
|
||||
|
||||
interface PrimaryFile {
|
||||
name: string
|
||||
fileType?: string
|
||||
existing?: boolean
|
||||
}
|
||||
|
||||
const primaryFile = computed<PrimaryFile | null>(() => {
|
||||
const existingPrimaryFile = draftVersion.value.existing_files?.[0]
|
||||
if (existingPrimaryFile) {
|
||||
return {
|
||||
name: existingPrimaryFile.filename,
|
||||
fileType: existingPrimaryFile.file_type,
|
||||
existing: true,
|
||||
}
|
||||
}
|
||||
|
||||
const addedPrimaryFile = filesToAdd.value[0]
|
||||
if (addedPrimaryFile) {
|
||||
return {
|
||||
name: addedPrimaryFile.file.name,
|
||||
fileType: addedPrimaryFile.fileType,
|
||||
existing: false,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
const supplementaryNewFiles = computed(() => {
|
||||
if (primaryFile.value?.existing) {
|
||||
return filesToAdd.value
|
||||
|
||||
+26
-127
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex w-full max-w-full flex-col gap-6 sm:w-[512px]">
|
||||
<div class="flex w-full max-w-full flex-col gap-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Add dependency</span>
|
||||
<div class="flex flex-col gap-3 rounded-2xl border border-solid border-surface-5 p-4">
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast">Project <span class="text-red">*</span></span>
|
||||
<span class="font-semibold text-contrast">Project</span>
|
||||
<DependencySelect v-model="newDependencyProjectId" />
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ButtonStyled>
|
||||
<ButtonStyled color="green">
|
||||
<button
|
||||
class="self-start"
|
||||
:disabled="!newDependencyProjectId"
|
||||
@@ -55,28 +55,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SuggestedDependencies
|
||||
:suggested-dependencies="suggestedDependencies"
|
||||
@on-add-suggestion="handleAddSuggestedDependency"
|
||||
/>
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Suggested dependencies</span>
|
||||
<SuggestedDependencies @on-add-suggestion="handleAddSuggestedDependency" />
|
||||
</div>
|
||||
|
||||
<div v-if="addedDependencies.length" class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Added dependencies</span>
|
||||
<div class="5 flex flex-col gap-2">
|
||||
<template v-for="(dependency, index) in addedDependencies">
|
||||
<AddedDependencyRow
|
||||
v-if="dependency"
|
||||
:key="index"
|
||||
:project-id="dependency.projectId"
|
||||
:name="dependency.name"
|
||||
:icon="dependency.icon"
|
||||
:dependency-type="dependency.dependencyType"
|
||||
:version-name="dependency.versionName"
|
||||
@remove="() => removeDependency(index)"
|
||||
/>
|
||||
</template>
|
||||
<span v-if="!addedDependencies.length"> No dependencies added. </span>
|
||||
</div>
|
||||
<DependenciesList />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -88,19 +74,26 @@ import {
|
||||
Combobox,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
} from '@modrinth/ui'
|
||||
import type { DropdownOption } from '@modrinth/ui/src/components/base/Combobox.vue'
|
||||
import type { ComboboxOption } from '@modrinth/ui/src/components/base/Combobox.vue'
|
||||
|
||||
import DependencySelect from '~/components/ui/create-project-version/components/DependencySelect.vue'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
import AddedDependencyRow from '../components/AddedDependencyRow.vue'
|
||||
import DependenciesList from '../components/DependenciesList.vue'
|
||||
import SuggestedDependencies from '../components/SuggestedDependencies/SuggestedDependencies.vue'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { labrinth } = injectModrinthClient()
|
||||
|
||||
const {
|
||||
draftVersion,
|
||||
dependencyProjects,
|
||||
dependencyVersions,
|
||||
projectsFetchLoading,
|
||||
visibleSuggestedDependencies,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const errorNotification = (err: any) => {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
@@ -113,12 +106,7 @@ const newDependencyProjectId = ref<string>()
|
||||
const newDependencyType = ref<Labrinth.Versions.v2.DependencyType>('required')
|
||||
const newDependencyVersionId = ref<string | null>(null)
|
||||
|
||||
const newDependencyVersions = ref<DropdownOption<string>[]>([])
|
||||
|
||||
const projectsFetchLoading = ref(false)
|
||||
const suggestedDependencies = ref<
|
||||
Array<Labrinth.Versions.v3.Dependency & { name?: string; icon?: string; versionName?: string }>
|
||||
>([])
|
||||
const newDependencyVersions = ref<ComboboxOption<string>[]>([])
|
||||
|
||||
// reset to defaults when select different project
|
||||
watch(newDependencyProjectId, async () => {
|
||||
@@ -140,91 +128,6 @@ watch(newDependencyProjectId, async () => {
|
||||
}
|
||||
})
|
||||
|
||||
const { draftVersion, dependencyProjects, dependencyVersions, getProject, getVersion } =
|
||||
injectManageVersionContext()
|
||||
const { projectV2: project } = injectProjectPageContext()
|
||||
|
||||
const getSuggestedDependencies = async () => {
|
||||
try {
|
||||
suggestedDependencies.value = []
|
||||
|
||||
if (!draftVersion.value.game_versions?.length || !draftVersion.value.loaders?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const versions = await labrinth.versions_v3.getProjectVersions(project.value.id, {
|
||||
loaders: draftVersion.value.loaders,
|
||||
})
|
||||
|
||||
// Get the most recent matching version and extract its dependencies
|
||||
if (versions.length > 0) {
|
||||
const mostRecentVersion = versions[0]
|
||||
for (const dep of mostRecentVersion.dependencies) {
|
||||
suggestedDependencies.value.push({
|
||||
project_id: dep.project_id,
|
||||
version_id: dep.version_id,
|
||||
dependency_type: dep.dependency_type,
|
||||
file_name: dep.file_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`Failed to get versions for project ${project.value.id}:`, error)
|
||||
}
|
||||
|
||||
for (const dep of suggestedDependencies.value) {
|
||||
try {
|
||||
if (dep.project_id) {
|
||||
const proj = await getProject(dep.project_id)
|
||||
dep.name = proj.name
|
||||
dep.icon = proj.icon_url
|
||||
}
|
||||
|
||||
if (dep.version_id) {
|
||||
const version = await getVersion(dep.version_id)
|
||||
dep.versionName = version.name
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`Failed to fetch project/version data for dependency:`, error)
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
errorNotification(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSuggestedDependencies()
|
||||
})
|
||||
|
||||
watch(
|
||||
draftVersion,
|
||||
async (draftVersion) => {
|
||||
const deps = draftVersion.dependencies || []
|
||||
|
||||
for (const dep of deps) {
|
||||
if (dep?.project_id) {
|
||||
try {
|
||||
await getProject(dep.project_id)
|
||||
} catch (error: any) {
|
||||
errorNotification(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (dep?.version_id) {
|
||||
try {
|
||||
await getVersion(dep.version_id)
|
||||
} catch (error: any) {
|
||||
errorNotification(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
projectsFetchLoading.value = false
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
const addedDependencies = computed(() =>
|
||||
(draftVersion.value.dependencies || [])
|
||||
.map((dep) => {
|
||||
@@ -249,12 +152,13 @@ const addedDependencies = computed(() =>
|
||||
const addDependency = (dependency: Labrinth.Versions.v3.Dependency) => {
|
||||
if (!draftVersion.value.dependencies) draftVersion.value.dependencies = []
|
||||
|
||||
// already added
|
||||
if (
|
||||
draftVersion.value.dependencies.find(
|
||||
(d) => d.project_id === dependency.project_id && d.version_id === dependency.version_id,
|
||||
)
|
||||
) {
|
||||
const alreadyAdded = draftVersion.value.dependencies.some((existing) => {
|
||||
if (existing.project_id !== dependency.project_id) return false
|
||||
if (!existing.version_id && !dependency.version_id) return true
|
||||
return existing.version_id === dependency.version_id
|
||||
})
|
||||
|
||||
if (alreadyAdded) {
|
||||
addNotification({
|
||||
title: 'Dependency already added',
|
||||
text: 'You cannot add the same dependency twice.',
|
||||
@@ -268,11 +172,6 @@ const addDependency = (dependency: Labrinth.Versions.v3.Dependency) => {
|
||||
newDependencyProjectId.value = undefined
|
||||
}
|
||||
|
||||
const removeDependency = (index: number) => {
|
||||
if (!draftVersion.value.dependencies) return
|
||||
draftVersion.value.dependencies.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleAddSuggestedDependency = (dependency: Labrinth.Versions.v3.Dependency) => {
|
||||
draftVersion.value.dependencies?.push({
|
||||
project_id: dependency.project_id,
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="flex w-full flex-col gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast">
|
||||
Version type <span class="text-red">*</span>
|
||||
</span>
|
||||
<Chips
|
||||
v-model="draftVersion.version_type"
|
||||
:items="['release', 'beta', 'alpha']"
|
||||
:never-empty="true"
|
||||
:capitalize="true"
|
||||
:disabled="isUploading"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast">
|
||||
Version number <span class="text-red">*</span>
|
||||
</span>
|
||||
<input
|
||||
id="version-number"
|
||||
v-model="draftVersion.version_number"
|
||||
:disabled="isUploading"
|
||||
placeholder="Enter version number, e.g. 1.2.3-alpha.1"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
maxlength="32"
|
||||
/>
|
||||
<span> The version number differentiates this specific version from others. </span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version subtitle </span>
|
||||
<input
|
||||
id="version-number"
|
||||
v-model="draftVersion.name"
|
||||
placeholder="Enter subtitle..."
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
maxlength="256"
|
||||
:disabled="isUploading"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version changlog </span>
|
||||
|
||||
<div class="w-full">
|
||||
<MarkdownEditor
|
||||
v-model="draftVersion.changelog"
|
||||
:on-image-upload="onImageUpload"
|
||||
:min-height="150"
|
||||
:disabled="isUploading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Chips, MarkdownEditor } from '@modrinth/ui'
|
||||
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { draftVersion, isUploading } = injectManageVersionContext()
|
||||
|
||||
async function onImageUpload(file: File) {
|
||||
const response = await useImageUpload(file, { context: 'version' })
|
||||
return response.url
|
||||
}
|
||||
</script>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="space-y-6 sm:w-[512px]">
|
||||
<div class="space-y-6">
|
||||
<LoaderPicker
|
||||
v-model="draftVersion.loaders"
|
||||
:loaders="generatedState.loaders"
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 sm:w-[512px]">
|
||||
<div class="flex flex-col gap-6">
|
||||
<McVersionPicker v-model="draftVersion.game_versions" :game-versions="gameVersions" />
|
||||
<div v-if="draftVersion.game_versions.length" class="space-y-1">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div v-if="!editingVersion" class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Uploaded files </span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editFiles">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<ViewOnlyFileRow
|
||||
v-if="primaryFile"
|
||||
:key="primaryFile.name"
|
||||
:name="primaryFile.name"
|
||||
:is-primary="true"
|
||||
/>
|
||||
<ViewOnlyFileRow
|
||||
v-for="file in supplementaryNewFiles"
|
||||
:key="file.file.name"
|
||||
:name="file.file.name"
|
||||
:file-type="file.fileType"
|
||||
/>
|
||||
<ViewOnlyFileRow
|
||||
v-for="file in supplementaryExistingFiles"
|
||||
:key="file.filename"
|
||||
:name="file.filename"
|
||||
:file-type="file.file_type"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ usingDetectedLoaders ? 'Detected loaders' : 'Loaders' }}
|
||||
</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button
|
||||
v-tooltip="
|
||||
isModpack
|
||||
? 'Modpack loaders cannot be edited'
|
||||
: isResourcePack
|
||||
? 'Resource pack loaders cannot be edited'
|
||||
: undefined
|
||||
"
|
||||
:disabled="isModpack || isResourcePack"
|
||||
@click="editLoaders"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-1.5 gap-y-4 rounded-xl border border-solid border-surface-5 p-3 py-4"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<template
|
||||
v-for="loader in draftVersionLoaders.map((selectedLoader) =>
|
||||
loaders.find((loader) => selectedLoader === loader.name),
|
||||
)"
|
||||
>
|
||||
<TagItem
|
||||
v-if="loader"
|
||||
:key="`loader-${loader.name}`"
|
||||
class="border !border-solid border-surface-5 hover:no-underline"
|
||||
:style="`--_color: var(--color-platform-${loader.name})`"
|
||||
>
|
||||
<div v-html="loader.icon"></div>
|
||||
{{ formatCategory(loader.name) }}
|
||||
</TagItem>
|
||||
</template>
|
||||
|
||||
<span v-if="!draftVersion.loaders.length">No loaders selected.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ usingDetectedVersions ? 'Detected versions' : 'Versions' }}
|
||||
</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button
|
||||
v-tooltip="isModpack ? 'Modpack versions cannot be edited' : undefined"
|
||||
:disabled="isModpack"
|
||||
@click="editVersions"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex max-h-56 flex-col gap-1.5 gap-y-4 overflow-y-auto rounded-xl border border-solid border-surface-5 p-3 py-4"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<TagItem
|
||||
v-for="version in draftVersion.game_versions"
|
||||
:key="version"
|
||||
class="border !border-solid border-surface-5 hover:no-underline"
|
||||
>
|
||||
{{ version }}
|
||||
</TagItem>
|
||||
|
||||
<span v-if="!draftVersion.game_versions.length">No versions selected.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!noEnvironmentProject">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-contrast"> Environment </span>
|
||||
<UnknownIcon v-tooltip="'Pre-filled from a previous similar version'" />
|
||||
</div>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editEnvironment">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5 gap-y-4 rounded-xl bg-surface-2 p-3 py-4">
|
||||
<div v-if="draftVersion.environment" class="flex flex-col gap-1">
|
||||
<div class="font-semibold text-contrast">
|
||||
{{ environmentCopy.title }}
|
||||
</div>
|
||||
<div class="text-sm font-medium">{{ environmentCopy.description }}</div>
|
||||
</div>
|
||||
|
||||
<span v-else class="text-sm font-medium">No environment has been set.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="!noDependenciesProject">
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Suggested dependencies </span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editDependencies">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<SuggestedDependencies @on-add-suggestion="handleAddSuggestedDependency" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!visibleSuggestedDependencies.length || draftVersion.dependencies?.length"
|
||||
class="flex flex-col gap-1"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Dependencies </span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editDependencies">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div v-if="draftVersion.dependencies?.length" class="flex flex-col gap-4">
|
||||
<DependenciesList disable-remove />
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-1.5 gap-y-4 rounded-xl bg-surface-2 p-3 py-4">
|
||||
<span class="text-sm font-medium">No dependencies added.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { EditIcon, UnknownIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
ENVIRONMENTS_COPY,
|
||||
injectProjectPageContext,
|
||||
TagItem,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
|
||||
import { useGeneratedState } from '~/composables/generated'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
import DependenciesList from '../components/DependenciesList.vue'
|
||||
import SuggestedDependencies from '../components/SuggestedDependencies/SuggestedDependencies.vue'
|
||||
import ViewOnlyFileRow from '../components/ViewOnlyFileRow.vue'
|
||||
|
||||
const {
|
||||
draftVersion,
|
||||
inferredVersionData,
|
||||
projectType,
|
||||
noEnvironmentProject,
|
||||
noDependenciesProject,
|
||||
modal,
|
||||
filesToAdd,
|
||||
editingVersion,
|
||||
visibleSuggestedDependencies,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const { projectV2 } = injectProjectPageContext()
|
||||
|
||||
const generatedState = useGeneratedState()
|
||||
const loaders = computed(() => generatedState.value.loaders)
|
||||
const isModpack = computed(() => projectType.value === 'modpack')
|
||||
const isResourcePack = computed(
|
||||
() =>
|
||||
projectType.value === 'resourcepack' &&
|
||||
(projectV2.value?.project_type === 'resourcepack' ||
|
||||
projectV2.value?.project_type === 'project'),
|
||||
)
|
||||
|
||||
const draftVersionLoaders = computed(() =>
|
||||
[
|
||||
...new Set([...draftVersion.value.loaders, ...(draftVersion.value.mrpack_loaders ?? [])]),
|
||||
].filter((loader) => loader !== 'mrpack'),
|
||||
)
|
||||
|
||||
const editLoaders = () => {
|
||||
modal.value?.setStage('from-details-loaders')
|
||||
}
|
||||
const editVersions = () => {
|
||||
modal.value?.setStage('from-details-mc-versions')
|
||||
}
|
||||
const editEnvironment = () => {
|
||||
modal.value?.setStage('from-details-environment')
|
||||
}
|
||||
const editFiles = () => {
|
||||
modal.value?.setStage('from-details-files')
|
||||
}
|
||||
const editDependencies = () => {
|
||||
modal.value?.setStage('from-details-dependencies')
|
||||
}
|
||||
|
||||
const usingDetectedVersions = computed(() => {
|
||||
if (!inferredVersionData.value?.game_versions) return false
|
||||
|
||||
const versionsMatch =
|
||||
draftVersion.value.game_versions.length === inferredVersionData.value.game_versions.length &&
|
||||
draftVersion.value.game_versions.every((version) =>
|
||||
inferredVersionData.value?.game_versions?.includes(version),
|
||||
)
|
||||
|
||||
return versionsMatch
|
||||
})
|
||||
|
||||
const usingDetectedLoaders = computed(() => {
|
||||
if (!inferredVersionData.value?.loaders) return false
|
||||
|
||||
const loadersMatch =
|
||||
draftVersion.value.loaders.length === inferredVersionData.value.loaders.length &&
|
||||
draftVersion.value.loaders.every((loader) =>
|
||||
inferredVersionData.value?.loaders?.includes(loader),
|
||||
)
|
||||
|
||||
return loadersMatch
|
||||
})
|
||||
|
||||
interface PrimaryFile {
|
||||
name: string
|
||||
fileType?: string
|
||||
existing?: boolean
|
||||
}
|
||||
|
||||
const primaryFile = computed<PrimaryFile | null>(() => {
|
||||
const existingPrimaryFile = draftVersion.value.existing_files?.[0]
|
||||
if (existingPrimaryFile) {
|
||||
return {
|
||||
name: existingPrimaryFile.filename,
|
||||
fileType: existingPrimaryFile.file_type,
|
||||
existing: true,
|
||||
}
|
||||
}
|
||||
|
||||
const addedPrimaryFile = filesToAdd.value[0]
|
||||
if (addedPrimaryFile) {
|
||||
return {
|
||||
name: addedPrimaryFile.file.name,
|
||||
fileType: addedPrimaryFile.fileType,
|
||||
existing: false,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
const supplementaryNewFiles = computed(() => {
|
||||
if (primaryFile.value?.existing) {
|
||||
return filesToAdd.value
|
||||
} else {
|
||||
return filesToAdd.value.slice(1)
|
||||
}
|
||||
})
|
||||
|
||||
const supplementaryExistingFiles = computed(() => {
|
||||
if (primaryFile.value?.existing) {
|
||||
return draftVersion.value.existing_files?.slice(1)
|
||||
} else {
|
||||
return draftVersion.value.existing_files
|
||||
}
|
||||
})
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const noEnvironmentMessage = defineMessages({
|
||||
title: {
|
||||
id: 'version.environment.none.title',
|
||||
defaultMessage: 'No environment set',
|
||||
},
|
||||
description: {
|
||||
id: 'version.environment.none.description',
|
||||
defaultMessage: 'The environment for this version has not been specified.',
|
||||
},
|
||||
})
|
||||
|
||||
const unknownEnvironmentMessage = defineMessages({
|
||||
title: {
|
||||
id: 'version.environment.unknown.title',
|
||||
defaultMessage: 'Unknown environment',
|
||||
},
|
||||
description: {
|
||||
id: 'version.environment.unknown.description',
|
||||
defaultMessage: 'The environment: "{environment}" is not recognized.',
|
||||
},
|
||||
})
|
||||
|
||||
const environmentCopy = computed(() => {
|
||||
if (!draftVersion.value.environment) {
|
||||
return {
|
||||
title: formatMessage(noEnvironmentMessage.title),
|
||||
description: formatMessage(noEnvironmentMessage.description),
|
||||
}
|
||||
}
|
||||
|
||||
const envCopy = ENVIRONMENTS_COPY[draftVersion.value.environment]
|
||||
if (envCopy) {
|
||||
return {
|
||||
title: formatMessage(envCopy.title),
|
||||
description: formatMessage(envCopy.description),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatMessage(unknownEnvironmentMessage.title),
|
||||
description: formatMessage(unknownEnvironmentMessage.description, {
|
||||
environment: draftVersion.value.environment,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const handleAddSuggestedDependency = (dependency: Labrinth.Versions.v3.Dependency) => {
|
||||
if (!draftVersion.value.dependencies) draftVersion.value.dependencies = []
|
||||
draftVersion.value.dependencies.push({
|
||||
project_id: dependency.project_id,
|
||||
version_id: dependency.version_id,
|
||||
dependency_type: dependency.dependency_type,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -12,14 +12,14 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownGiftCardValue) }}</span>
|
||||
<span class="font-semibold text-contrast"
|
||||
>{{ formatMoney(amount || 0) }} ({{ formattedLocalCurrency }})</span
|
||||
>{{ formatMoney(amountInUsd) }} ({{ formattedLocalCurrencyAmount }})</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownAmount) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ formatMoney(amount || 0) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ formatMoney(amountInUsd) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<template v-if="feeLoading">
|
||||
<LoaderCircleIcon class="size-5 animate-spin !text-secondary" />
|
||||
</template>
|
||||
<template v-else>-{{ formatMoney(fee || 0) }}</template>
|
||||
<template v-else>-{{ formatMoney(feeInUsd) }}</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -79,9 +79,23 @@ const props = withDefaults(
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const amountInUsd = computed(() => {
|
||||
if (props.isGiftCard && shouldShowExchangeRate.value) {
|
||||
return (props.amount || 0) / (props.exchangeRate || 1)
|
||||
}
|
||||
return props.amount || 0
|
||||
})
|
||||
|
||||
const feeInUsd = computed(() => {
|
||||
if (props.isGiftCard && shouldShowExchangeRate.value) {
|
||||
return (props.fee || 0) / (props.exchangeRate || 1)
|
||||
}
|
||||
return props.fee || 0
|
||||
})
|
||||
|
||||
const netAmount = computed(() => {
|
||||
const amount = props.amount || 0
|
||||
const fee = props.fee || 0
|
||||
const amount = amountInUsd.value
|
||||
const fee = feeInUsd.value
|
||||
return Math.max(0, amount - fee)
|
||||
})
|
||||
|
||||
@@ -96,6 +110,11 @@ const netAmountInLocalCurrency = computed(() => {
|
||||
return netAmount.value * (props.exchangeRate || 0)
|
||||
})
|
||||
|
||||
const localCurrencyAmount = computed(() => {
|
||||
if (!shouldShowExchangeRate.value) return null
|
||||
return props.amount || 0
|
||||
})
|
||||
|
||||
const formattedLocalCurrency = computed(() => {
|
||||
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !props.localCurrency)
|
||||
return ''
|
||||
@@ -112,6 +131,21 @@ const formattedLocalCurrency = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const formattedLocalCurrencyAmount = computed(() => {
|
||||
if (!shouldShowExchangeRate.value || !localCurrencyAmount.value || !props.localCurrency) return ''
|
||||
|
||||
try {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: props.localCurrency,
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(localCurrencyAmount.value)
|
||||
} catch {
|
||||
return `${props.localCurrency} ${localCurrencyAmount.value.toFixed(2)}`
|
||||
}
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
feeBreakdownAmount: {
|
||||
id: 'dashboard.creator-withdraw-modal.fee-breakdown-amount',
|
||||
|
||||
+30
-10
@@ -90,7 +90,14 @@
|
||||
</Combobox>
|
||||
</div>
|
||||
<span v-if="selectedMethodDetails" class="text-secondary">
|
||||
{{ formatMoney(fixedDenominationMin ?? effectiveMinAmount)
|
||||
{{
|
||||
formatMoney(
|
||||
selectedMethodCurrencyCode &&
|
||||
selectedMethodCurrencyCode !== 'USD' &&
|
||||
selectedMethodExchangeRate
|
||||
? (fixedDenominationMin ?? effectiveMinAmount) / selectedMethodExchangeRate
|
||||
: (fixedDenominationMin ?? effectiveMinAmount),
|
||||
)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
|
||||
({{
|
||||
formatAmountForDisplay(
|
||||
@@ -103,9 +110,15 @@
|
||||
min,
|
||||
{{
|
||||
formatMoney(
|
||||
fixedDenominationMax ??
|
||||
selectedMethodDetails.interval?.standard?.max ??
|
||||
effectiveMaxAmount,
|
||||
selectedMethodCurrencyCode &&
|
||||
selectedMethodCurrencyCode !== 'USD' &&
|
||||
selectedMethodExchangeRate
|
||||
? (fixedDenominationMax ??
|
||||
selectedMethodDetails.interval?.standard?.max ??
|
||||
effectiveMaxAmount) / selectedMethodExchangeRate
|
||||
: (fixedDenominationMax ??
|
||||
selectedMethodDetails.interval?.standard?.max ??
|
||||
effectiveMaxAmount),
|
||||
)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
|
||||
({{
|
||||
@@ -124,7 +137,15 @@
|
||||
v-if="selectedMethodDetails && effectiveMinAmount > roundedMaxAmount"
|
||||
class="text-sm text-red"
|
||||
>
|
||||
You need at least {{ formatMoney(effectiveMinAmount)
|
||||
You need at least
|
||||
{{
|
||||
formatMoney(
|
||||
selectedMethodCurrencyCode &&
|
||||
selectedMethodCurrencyCode !== 'USD' &&
|
||||
selectedMethodExchangeRate
|
||||
? effectiveMinAmount / selectedMethodExchangeRate
|
||||
: effectiveMinAmount,
|
||||
)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
|
||||
({{
|
||||
formatAmountForDisplay(
|
||||
@@ -186,7 +207,7 @@
|
||||
formatMessage(messages.balanceWorthHint, {
|
||||
usdBalance: formatMoney(roundedMaxAmount),
|
||||
localBalance: formatAmountForDisplay(
|
||||
roundedMaxAmount,
|
||||
roundedMaxAmount * selectedMethodExchangeRate,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
),
|
||||
@@ -252,7 +273,7 @@
|
||||
formatMessage(messages.balanceWorthHint, {
|
||||
usdBalance: formatMoney(roundedMaxAmount),
|
||||
localBalance: formatAmountForDisplay(
|
||||
roundedMaxAmount,
|
||||
roundedMaxAmount * selectedMethodExchangeRate,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
),
|
||||
@@ -573,14 +594,13 @@ const giftCardExchangeRate = computed(() => {
|
||||
})
|
||||
|
||||
function formatAmountForDisplay(
|
||||
usdAmount: number,
|
||||
localAmount: number,
|
||||
currencyCode: string | null | undefined,
|
||||
rate: number | null | undefined,
|
||||
): string {
|
||||
if (!currencyCode || currencyCode === 'USD' || !rate) {
|
||||
return formatMoney(usdAmount)
|
||||
return formatMoney(localAmount)
|
||||
}
|
||||
const localAmount = usdAmount * rate
|
||||
try {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { ClipboardCopyIcon, LoaderCircleIcon, XIcon } from '@modrinth/assets'
|
||||
import { ClipboardCopyIcon, DownloadIcon, LoaderCircleIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, CopyCode, NewModal } from '@modrinth/ui'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
|
||||
@@ -38,15 +38,16 @@ async function fetchVersionHashes(versionIds: string[]) {
|
||||
// TODO: switch to api-client once truman's vers stuff is merged
|
||||
const version = (await useBaseFetch(`version/${versionId}`)) as {
|
||||
files: Array<{
|
||||
id?: string
|
||||
filename: string
|
||||
file_name?: string
|
||||
hashes: { sha512: string; sha1: string }
|
||||
}>
|
||||
}
|
||||
const filesMap = new Map<string, string>()
|
||||
for (const file of version.files) {
|
||||
const name = file.file_name ?? file.filename
|
||||
filesMap.set(name, file.hashes.sha512)
|
||||
if (file.id) {
|
||||
filesMap.set(file.id, file.hashes.sha512)
|
||||
}
|
||||
}
|
||||
versionDataCache.value.set(versionId, { files: filesMap, loading: false })
|
||||
} catch (error) {
|
||||
@@ -60,8 +61,8 @@ async function fetchVersionHashes(versionIds: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function getFileHash(versionId: string, fileName: string): string | undefined {
|
||||
return versionDataCache.value.get(versionId)?.files.get(fileName)
|
||||
function getFileHash(versionId: string, fileId: string): string | undefined {
|
||||
return versionDataCache.value.get(versionId)?.files.get(fileId)
|
||||
}
|
||||
|
||||
function isHashLoading(versionId: string): boolean {
|
||||
@@ -114,6 +115,7 @@ defineExpose({ show, hide })
|
||||
<th class="pb-2">Version ID</th>
|
||||
<th class="pb-2">File Name</th>
|
||||
<th class="pb-2">CDN Link</th>
|
||||
<th class="pb-2">Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -124,11 +126,11 @@ defineExpose({ show, hide })
|
||||
class="size-4 animate-spin text-secondary"
|
||||
/>
|
||||
<ButtonStyled
|
||||
v-else-if="getFileHash(item.file.version_id, item.file.file_name)"
|
||||
v-else-if="getFileHash(item.file.version_id, item.file.file_id)"
|
||||
size="small"
|
||||
type="standard"
|
||||
>
|
||||
<button @click="copy(getFileHash(item.file.version_id, item.file.file_name)!)">
|
||||
<button @click="copy(getFileHash(item.file.version_id, item.file.file_id)!)">
|
||||
<ClipboardCopyIcon class="size-4" />
|
||||
Copy
|
||||
</button>
|
||||
@@ -141,7 +143,7 @@ defineExpose({ show, hide })
|
||||
<td class="py-1 pr-2">
|
||||
<CopyCode :text="item.file.file_name" />
|
||||
</td>
|
||||
<td class="py-1">
|
||||
<td class="py-1 pr-2">
|
||||
<ButtonStyled size="small" type="standard">
|
||||
<button @click="copy(item.file.download_url)">
|
||||
<ClipboardCopyIcon class="size-4" />
|
||||
@@ -149,6 +151,13 @@ defineExpose({ show, hide })
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</td>
|
||||
<td class="py-1">
|
||||
<ButtonStyled circular size="small">
|
||||
<a :href="item.file.download_url" :download="item.file.file_name" target="_blank">
|
||||
<DownloadIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -127,11 +127,9 @@ import dayjs from 'dayjs'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { ModerationProject } from '~/helpers/moderation'
|
||||
import { useModerationStore } from '~/store/moderation.ts'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const moderationStore = useModerationStore()
|
||||
|
||||
const baseId = useId()
|
||||
|
||||
@@ -139,6 +137,10 @@ const props = defineProps<{
|
||||
queueEntry: ModerationProject
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
startFromProject: [projectId: string]
|
||||
}>()
|
||||
|
||||
function getDaysQueued(date: Date): number {
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
@@ -201,16 +203,6 @@ const quickActions: OverflowMenuOption[] = [
|
||||
]
|
||||
|
||||
function openProjectForReview() {
|
||||
moderationStore.setSingleProject(props.queueEntry.project.id)
|
||||
navigateTo({
|
||||
name: 'type-id',
|
||||
params: {
|
||||
type: 'project',
|
||||
id: props.queueEntry.project.slug || props.queueEntry.project.id,
|
||||
},
|
||||
state: {
|
||||
showChecklist: true,
|
||||
},
|
||||
})
|
||||
emit('startFromProject', props.queueEntry.project.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
LinkIcon,
|
||||
LoaderCircleIcon,
|
||||
ShieldCheckIcon,
|
||||
TimerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { type TechReviewContext, techReviewQuickReplies } from '@modrinth/moderation'
|
||||
import {
|
||||
@@ -113,8 +114,8 @@ const quickActions = computed<OverflowMenuOption[]>(() => {
|
||||
navigator.clipboard.writeText(props.item.project.id).then(() => {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Technical Report ID copied',
|
||||
text: 'The ID of this report has been copied to your clipboard.',
|
||||
title: 'Project ID copied',
|
||||
text: 'The ID of this project has been copied to your clipboard.',
|
||||
})
|
||||
})
|
||||
},
|
||||
@@ -265,6 +266,13 @@ const severityColor = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const isProjectApproved = computed(() => {
|
||||
const status = props.item.project.status
|
||||
return (
|
||||
status === 'approved' || status === 'archived' || status === 'unlisted' || status === 'private'
|
||||
)
|
||||
})
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
const dates = props.item.reports.map((r) => new Date(r.created))
|
||||
const earliest = new Date(Math.min(...dates.map((d) => d.getTime())))
|
||||
@@ -369,12 +377,16 @@ async function updateDetailStatus(detailId: string, verdict: 'safe' | 'unsafe')
|
||||
if (detailKey) break
|
||||
}
|
||||
|
||||
let otherMatchedCount = 0
|
||||
if (detailKey) {
|
||||
for (const report of props.item.reports) {
|
||||
for (const issue of report.issues) {
|
||||
for (const detail of issue.details) {
|
||||
if (detail.key === detailKey) {
|
||||
detailDecisions.value.set(detail.id, decision)
|
||||
if (detail.id !== detailId) {
|
||||
otherMatchedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,17 +403,31 @@ async function updateDetailStatus(detailId: string, verdict: 'safe' | 'unsafe')
|
||||
}
|
||||
}
|
||||
|
||||
// Jump back to Files tab when all flags in the current file are marked
|
||||
if (selectedFile.value) {
|
||||
const markedCount = getFileMarkedCount(selectedFile.value)
|
||||
const totalCount = getFileDetailCount(selectedFile.value)
|
||||
if (markedCount === totalCount) {
|
||||
backToFileList()
|
||||
}
|
||||
}
|
||||
|
||||
const otherText =
|
||||
otherMatchedCount > 0
|
||||
? ` (${otherMatchedCount} other trace${otherMatchedCount === 1 ? '' : 's'} also marked)`
|
||||
: ''
|
||||
|
||||
if (verdict === 'safe') {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Issue marked as pass',
|
||||
text: 'This issue has been marked as a false positive.',
|
||||
text: `This issue has been marked as a false positive.${otherText}`,
|
||||
})
|
||||
} else {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Issue marked as fail',
|
||||
text: 'This issue has been flagged as malicious.',
|
||||
text: `This issue has been flagged as malicious.${otherText}`,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -472,6 +498,17 @@ const groupedByClass = computed<ClassGroup[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// Auto-expand if there's only one class in the file
|
||||
watch(
|
||||
groupedByClass,
|
||||
(classes) => {
|
||||
if (classes.length === 1) {
|
||||
expandedClasses.value.add(classes[0].filePath)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function getHighestSeverityInClass(
|
||||
flags: ClassGroup['flags'],
|
||||
): Labrinth.TechReview.Internal.DelphiSeverity {
|
||||
@@ -623,7 +660,7 @@ const threadWithPreview = computed(() => {
|
||||
body: {
|
||||
type: 'text',
|
||||
body: reviewSummaryPreview.value,
|
||||
private: false,
|
||||
private: true,
|
||||
replying_to: null,
|
||||
associated_images: [],
|
||||
},
|
||||
@@ -747,9 +784,21 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-full border border-solid border-surface-5 bg-surface-4 px-2.5 py-1"
|
||||
class="flex items-center gap-1 rounded-full border border-solid px-2.5 py-1"
|
||||
:class="
|
||||
isProjectApproved
|
||||
? 'border-green bg-highlight-green'
|
||||
: 'border-orange bg-highlight-orange'
|
||||
"
|
||||
>
|
||||
<span class="text-sm font-medium text-secondary">Auto-Flagged</span>
|
||||
<CheckIcon v-if="isProjectApproved" aria-hidden="true" class="h-4 w-4 text-green" />
|
||||
<TimerIcon v-else aria-hidden="true" class="h-4 w-4 text-orange" />
|
||||
<span
|
||||
class="text-sm font-medium"
|
||||
:class="isProjectApproved ? 'text-green' : 'text-orange'"
|
||||
>
|
||||
{{ isProjectApproved ? 'Live' : 'In review' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="rounded-full px-2.5 py-1" :class="severityColor">
|
||||
@@ -929,8 +978,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
:href="file.download_url"
|
||||
:title="`Download ${file.file_name}`"
|
||||
:download="file.file_name"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="!border-px !border-surface-4"
|
||||
tabindex="0"
|
||||
>
|
||||
@@ -1008,15 +1055,21 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
v-for="flag in classItem.flags"
|
||||
:key="`${flag.issueId}-${flag.detail.id}`"
|
||||
class="grid grid-cols-[1fr_auto_auto] items-center rounded-lg border-[1px] border-b border-solid border-surface-5 bg-surface-3 py-2 pl-4 last:border-b-0"
|
||||
:class="{
|
||||
'opacity-50': isPreReviewed(flag.detail.id, flag.detail.status),
|
||||
}"
|
||||
>
|
||||
<span class="text-base font-semibold text-contrast">{{
|
||||
flag.issueType.replace(/_/g, ' ')
|
||||
}}</span>
|
||||
<span
|
||||
class="text-base font-semibold text-contrast"
|
||||
:class="{
|
||||
'opacity-50': isPreReviewed(flag.detail.id, flag.detail.status),
|
||||
}"
|
||||
>{{ flag.issueType.replace(/_/g, ' ') }}</span
|
||||
>
|
||||
|
||||
<div class="flex w-20 justify-center">
|
||||
<div
|
||||
class="flex w-20 justify-center"
|
||||
:class="{
|
||||
'opacity-50': isPreReviewed(flag.detail.id, flag.detail.status),
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="rounded-full border-solid px-2.5 py-1"
|
||||
:class="getSeverityBadgeColor(flag.detail.severity)"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -237,7 +237,7 @@ function navigateToFolder() {
|
||||
const newPath = currentPath.endsWith('/')
|
||||
? `${currentPath}${props.name}`
|
||||
: `${currentPath}/${props.name}`
|
||||
router.push({ query: { path: newPath, page: 1 } })
|
||||
router.push({ query: { path: newPath } })
|
||||
}
|
||||
|
||||
const isNavigating = ref(false)
|
||||
|
||||
@@ -42,22 +42,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
hideRussiaCensorshipBanner: false,
|
||||
serverDiscovery: false,
|
||||
disablePrettyProjectUrlRedirects: false,
|
||||
// advancedRendering: true,
|
||||
// externalLinksNewTab: true,
|
||||
// notUsingBlockers: false,
|
||||
// hideModrinthAppPromos: false,
|
||||
// preferredDarkTheme: 'dark',
|
||||
// hideStagingBanner: false,
|
||||
|
||||
// Project display modes
|
||||
// modSearchDisplayMode: 'list',
|
||||
// pluginSearchDisplayMode: 'list',
|
||||
// resourcePackSearchDisplayMode: 'gallery',
|
||||
// modpackSearchDisplayMode: 'list',
|
||||
// shaderSearchDisplayMode: 'gallery',
|
||||
// dataPackSearchDisplayMode: 'list',
|
||||
// userProjectDisplayMode: 'list',
|
||||
// collectionProjectDisplayMode: 'list',
|
||||
hidePreviewBanner: false,
|
||||
} as const)
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
|
||||
@@ -55,5 +55,8 @@ export const useBaseFetch = async (url, options = {}, skipAuth = false) => {
|
||||
delete options.apiVersion
|
||||
}
|
||||
|
||||
return await $fetch(`${base}${url}`, options)
|
||||
return await $fetch(`${base}${url}`, {
|
||||
timeout: import.meta.server ? 10000 : undefined,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,514 +0,0 @@
|
||||
import { parse as parseTOML } from '@ltd/j-toml'
|
||||
import yaml from 'js-yaml'
|
||||
import JSZip from 'jszip'
|
||||
import { satisfies } from 'semver'
|
||||
|
||||
export const inferVersionInfo = async function (rawFile, project, gameVersions) {
|
||||
function versionType(number) {
|
||||
if (number.includes('alpha')) {
|
||||
return 'alpha'
|
||||
} else if (
|
||||
number.includes('beta') ||
|
||||
number.match(/[^A-z](rc)[^A-z]/) || // includes `rc`
|
||||
number.match(/[^A-z](pre)[^A-z]/) // includes `pre`
|
||||
) {
|
||||
return 'beta'
|
||||
} else {
|
||||
return 'release'
|
||||
}
|
||||
}
|
||||
|
||||
function getGameVersionsMatchingSemverRange(range, gameVersions) {
|
||||
if (!range) {
|
||||
return []
|
||||
}
|
||||
const ranges = Array.isArray(range) ? range : [range]
|
||||
return gameVersions.filter((version) => {
|
||||
const semverVersion = version.split('.').length === 2 ? `${version}.0` : version // add patch version if missing (e.g. 1.16 -> 1.16.0)
|
||||
return ranges.some((v) => satisfies(semverVersion, v))
|
||||
})
|
||||
}
|
||||
|
||||
function getGameVersionsMatchingMavenRange(range, gameVersions) {
|
||||
if (!range) {
|
||||
return []
|
||||
}
|
||||
const ranges = []
|
||||
|
||||
while (range.startsWith('[') || range.startsWith('(')) {
|
||||
let index = range.indexOf(')')
|
||||
const index2 = range.indexOf(']')
|
||||
if (index === -1 || (index2 !== -1 && index2 < index)) {
|
||||
index = index2
|
||||
}
|
||||
if (index === -1) break
|
||||
ranges.push(range.substring(0, index + 1))
|
||||
range = range.substring(index + 1).trim()
|
||||
if (range.startsWith(',')) {
|
||||
range = range.substring(1).trim()
|
||||
}
|
||||
}
|
||||
|
||||
if (range) {
|
||||
ranges.push(range)
|
||||
}
|
||||
|
||||
const LESS_THAN_EQUAL = /^\(,(.*)]$/
|
||||
const LESS_THAN = /^\(,(.*)\)$/
|
||||
const EQUAL = /^\[(.*)]$/
|
||||
const GREATER_THAN_EQUAL = /^\[(.*),\)$/
|
||||
const GREATER_THAN = /^\((.*),\)$/
|
||||
const BETWEEN = /^\((.*),(.*)\)$/
|
||||
const BETWEEN_EQUAL = /^\[(.*),(.*)]$/
|
||||
const BETWEEN_LESS_THAN_EQUAL = /^\((.*),(.*)]$/
|
||||
const BETWEEN_GREATER_THAN_EQUAL = /^\[(.*),(.*)\)$/
|
||||
|
||||
const semverRanges = []
|
||||
|
||||
for (const range of ranges) {
|
||||
let result
|
||||
if ((result = range.match(LESS_THAN_EQUAL))) {
|
||||
semverRanges.push(`<=${result[1]}`)
|
||||
} else if ((result = range.match(LESS_THAN))) {
|
||||
semverRanges.push(`<${result[1]}`)
|
||||
} else if ((result = range.match(EQUAL))) {
|
||||
semverRanges.push(`${result[1]}`)
|
||||
} else if ((result = range.match(GREATER_THAN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]}`)
|
||||
} else if ((result = range.match(GREATER_THAN))) {
|
||||
semverRanges.push(`>${result[1]}`)
|
||||
} else if ((result = range.match(BETWEEN))) {
|
||||
semverRanges.push(`>${result[1]} <${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]} <=${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_LESS_THAN_EQUAL))) {
|
||||
semverRanges.push(`>${result[1]} <=${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_GREATER_THAN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]} <${result[2]}`)
|
||||
}
|
||||
}
|
||||
return getGameVersionsMatchingSemverRange(semverRanges, gameVersions)
|
||||
}
|
||||
|
||||
const simplifiedGameVersions = gameVersions
|
||||
.filter((it) => it.version_type === 'release')
|
||||
.map((it) => it.version)
|
||||
|
||||
const inferFunctions = {
|
||||
// NeoForge
|
||||
'META-INF/neoforge.mods.toml': (file) => {
|
||||
const metadata = parseTOML(file, { joiner: '\n' })
|
||||
if (!metadata.mods || metadata.mods.length === 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const neoForgeDependency = Object.values(metadata.dependencies)
|
||||
.flat()
|
||||
.find((dependency) => dependency.modId === 'neoforge')
|
||||
if (!neoForgeDependency) {
|
||||
return {}
|
||||
}
|
||||
|
||||
// https://docs.neoforged.net/docs/gettingstarted/versioning/#neoforge
|
||||
const mcVersionRange = neoForgeDependency.versionRange
|
||||
.replace('-beta', '')
|
||||
.replace(/(\d+)(?:\.(\d+))?(?:\.(\d+)?)?/g, (_match, major, minor) => {
|
||||
return `1.${major}${minor ? '.' + minor : ''}`
|
||||
})
|
||||
const gameVersions = getGameVersionsMatchingMavenRange(mcVersionRange, simplifiedGameVersions)
|
||||
|
||||
const versionNum = metadata.mods[0].version
|
||||
return {
|
||||
name: `${project.title} ${versionNum}`,
|
||||
version_number: versionNum,
|
||||
loaders: ['neoforge'],
|
||||
version_type: versionType(versionNum),
|
||||
game_versions: gameVersions,
|
||||
}
|
||||
},
|
||||
// Forge 1.13+
|
||||
'META-INF/mods.toml': async (file, zip) => {
|
||||
const metadata = parseTOML(file, { joiner: '\n' })
|
||||
|
||||
if (metadata.mods && metadata.mods.length > 0) {
|
||||
let versionNum = metadata.mods[0].version
|
||||
|
||||
// ${file.jarVersion} -> Implementation-Version from manifest
|
||||
const manifestFile = zip.file('META-INF/MANIFEST.MF')
|
||||
if (metadata.mods[0].version.includes('${file.jarVersion}') && manifestFile !== null) {
|
||||
const manifestText = await manifestFile.async('text')
|
||||
const regex = /Implementation-Version: (.*)$/m
|
||||
const match = manifestText.match(regex)
|
||||
if (match) {
|
||||
versionNum = versionNum.replace('${file.jarVersion}', match[1])
|
||||
}
|
||||
}
|
||||
|
||||
let gameVersions = []
|
||||
const mcDependencies = Object.values(metadata.dependencies)
|
||||
.flat()
|
||||
.filter((dependency) => dependency.modId === 'minecraft')
|
||||
|
||||
if (mcDependencies.length > 0) {
|
||||
gameVersions = getGameVersionsMatchingMavenRange(
|
||||
mcDependencies[0].versionRange,
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title} ${versionNum}`,
|
||||
version_number: versionNum,
|
||||
version_type: versionType(versionNum),
|
||||
loaders: ['forge'],
|
||||
game_versions: gameVersions,
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// Old Forge
|
||||
'mcmod.info': (file) => {
|
||||
const metadata = JSON.parse(file)
|
||||
|
||||
return {
|
||||
name: metadata.version ? `${project.title} ${metadata.version}` : '',
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['forge'],
|
||||
game_versions: simplifiedGameVersions.filter((version) =>
|
||||
version.startsWith(metadata.mcversion),
|
||||
),
|
||||
}
|
||||
},
|
||||
// Fabric
|
||||
'fabric.mod.json': (file) => {
|
||||
const metadata = JSON.parse(file)
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
loaders: ['fabric'],
|
||||
version_type: versionType(metadata.version),
|
||||
game_versions: metadata.depends
|
||||
? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions)
|
||||
: [],
|
||||
}
|
||||
},
|
||||
// Quilt
|
||||
'quilt.mod.json': (file) => {
|
||||
const metadata = JSON.parse(file)
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.quilt_loader.version}`,
|
||||
version_number: metadata.quilt_loader.version,
|
||||
loaders: ['quilt'],
|
||||
version_type: versionType(metadata.quilt_loader.version),
|
||||
game_versions: metadata.quilt_loader.depends
|
||||
? getGameVersionsMatchingSemverRange(
|
||||
metadata.quilt_loader.depends.find((x) => x.id === 'minecraft')
|
||||
? metadata.quilt_loader.depends.find((x) => x.id === 'minecraft').versions
|
||||
: [],
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
: [],
|
||||
}
|
||||
},
|
||||
// Bukkit + Other Forks
|
||||
'plugin.yml': (file) => {
|
||||
const metadata = yaml.load(file)
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
// We don't know which fork of Bukkit users are using
|
||||
loaders: [],
|
||||
game_versions: gameVersions
|
||||
.filter(
|
||||
(x) => x.version.startsWith(metadata['api-version']) && x.version_type === 'release',
|
||||
)
|
||||
.map((x) => x.version),
|
||||
}
|
||||
},
|
||||
// Paper 1.19.3+
|
||||
'paper-plugin.yml': (file) => {
|
||||
const metadata = yaml.load(file)
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['paper'],
|
||||
game_versions: gameVersions
|
||||
.filter(
|
||||
(x) => x.version.startsWith(metadata['api-version']) && x.version_type === 'release',
|
||||
)
|
||||
.map((x) => x.version),
|
||||
}
|
||||
},
|
||||
// Bungeecord + Waterfall
|
||||
'bungee.yml': (file) => {
|
||||
const metadata = yaml.load(file)
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['bungeecord'],
|
||||
}
|
||||
},
|
||||
// Velocity
|
||||
'velocity-plugin.json': (file) => {
|
||||
const metadata = JSON.parse(file)
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['velocity'],
|
||||
}
|
||||
},
|
||||
// Modpacks
|
||||
'modrinth.index.json': (file) => {
|
||||
const metadata = JSON.parse(file)
|
||||
|
||||
const loaders = []
|
||||
if ('forge' in metadata.dependencies) {
|
||||
loaders.push('forge')
|
||||
}
|
||||
if ('neoforge' in metadata.dependencies) {
|
||||
loaders.push('neoforge')
|
||||
}
|
||||
if ('fabric-loader' in metadata.dependencies) {
|
||||
loaders.push('fabric')
|
||||
}
|
||||
if ('quilt-loader' in metadata.dependencies) {
|
||||
loaders.push('quilt')
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.versionId}`,
|
||||
version_number: metadata.versionId,
|
||||
version_type: versionType(metadata.versionId),
|
||||
loaders,
|
||||
game_versions: gameVersions
|
||||
.filter((x) => x.version === metadata.dependencies.minecraft)
|
||||
.map((x) => x.version),
|
||||
}
|
||||
},
|
||||
// Resource Packs + Data Packs
|
||||
'pack.mcmeta': (file) => {
|
||||
const metadata = JSON.parse(file)
|
||||
|
||||
function getRange(versionA, versionB) {
|
||||
const startingIndex = gameVersions.findIndex((x) => x.version === versionA)
|
||||
const endingIndex = gameVersions.findIndex((x) => x.version === versionB)
|
||||
|
||||
const final = []
|
||||
const filterOnlyRelease = gameVersions[startingIndex].version_type === 'release'
|
||||
|
||||
for (let i = startingIndex; i >= endingIndex; i--) {
|
||||
if (gameVersions[i].version_type === 'release' || !filterOnlyRelease) {
|
||||
final.push(gameVersions[i].version)
|
||||
}
|
||||
}
|
||||
|
||||
return final
|
||||
}
|
||||
|
||||
const loaders = []
|
||||
let newGameVersions = []
|
||||
|
||||
if (project.actualProjectType === 'mod') {
|
||||
loaders.push('datapack')
|
||||
|
||||
switch (metadata.pack.pack_format) {
|
||||
case 4:
|
||||
newGameVersions = getRange('1.13', '1.14.4')
|
||||
break
|
||||
case 5:
|
||||
newGameVersions = getRange('1.15', '1.16.1')
|
||||
break
|
||||
case 6:
|
||||
newGameVersions = getRange('1.16.2', '1.16.5')
|
||||
break
|
||||
case 7:
|
||||
newGameVersions = getRange('1.17', '1.17.1')
|
||||
break
|
||||
case 8:
|
||||
newGameVersions = getRange('1.18', '1.18.1')
|
||||
break
|
||||
case 9:
|
||||
newGameVersions.push('1.18.2')
|
||||
break
|
||||
case 10:
|
||||
newGameVersions = getRange('1.19', '1.19.3')
|
||||
break
|
||||
case 11:
|
||||
newGameVersions = getRange('23w03a', '23w05a')
|
||||
break
|
||||
case 12:
|
||||
newGameVersions.push('1.19.4')
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (project.actualProjectType === 'resourcepack') {
|
||||
loaders.push('minecraft')
|
||||
|
||||
switch (metadata.pack.pack_format) {
|
||||
case 1:
|
||||
newGameVersions = getRange('1.6.1', '1.8.9')
|
||||
break
|
||||
case 2:
|
||||
newGameVersions = getRange('1.9', '1.10.2')
|
||||
break
|
||||
case 3:
|
||||
newGameVersions = getRange('1.11', '1.12.2')
|
||||
break
|
||||
case 4:
|
||||
newGameVersions = getRange('1.13', '1.14.4')
|
||||
break
|
||||
case 5:
|
||||
newGameVersions = getRange('1.15', '1.16.1')
|
||||
break
|
||||
case 6:
|
||||
newGameVersions = getRange('1.16.2', '1.16.5')
|
||||
break
|
||||
case 7:
|
||||
newGameVersions = getRange('1.17', '1.17.1')
|
||||
break
|
||||
case 8:
|
||||
newGameVersions = getRange('1.18', '1.18.2')
|
||||
break
|
||||
case 9:
|
||||
newGameVersions = getRange('1.19', '1.19.2')
|
||||
break
|
||||
case 11:
|
||||
newGameVersions = getRange('22w42a', '22w44a')
|
||||
break
|
||||
case 12:
|
||||
newGameVersions.push('1.19.3')
|
||||
break
|
||||
case 13:
|
||||
newGameVersions.push('1.19.4')
|
||||
break
|
||||
case 14:
|
||||
newGameVersions = getRange('23w14a', '23w16a')
|
||||
break
|
||||
case 15:
|
||||
newGameVersions = getRange('1.20', '1.20.1')
|
||||
break
|
||||
case 16:
|
||||
newGameVersions.push('23w31a')
|
||||
break
|
||||
case 17:
|
||||
newGameVersions = getRange('23w32a', '1.20.2-pre1')
|
||||
break
|
||||
case 18:
|
||||
newGameVersions.push('1.20.2')
|
||||
break
|
||||
case 19:
|
||||
newGameVersions.push('23w42a')
|
||||
break
|
||||
case 20:
|
||||
newGameVersions = getRange('23w43a', '23w44a')
|
||||
break
|
||||
case 21:
|
||||
newGameVersions = getRange('23w45a', '23w46a')
|
||||
break
|
||||
case 22:
|
||||
newGameVersions = getRange('1.20.3', '1.20.4')
|
||||
break
|
||||
case 24:
|
||||
newGameVersions = getRange('24w03a', '24w04a')
|
||||
break
|
||||
case 25:
|
||||
newGameVersions = getRange('24w05a', '24w05b')
|
||||
break
|
||||
case 26:
|
||||
newGameVersions = getRange('24w06a', '24w07a')
|
||||
break
|
||||
case 28:
|
||||
newGameVersions = getRange('24w09a', '24w10a')
|
||||
break
|
||||
case 29:
|
||||
newGameVersions.push('24w11a')
|
||||
break
|
||||
case 30:
|
||||
newGameVersions.push('24w12a')
|
||||
break
|
||||
case 31:
|
||||
newGameVersions = getRange('24w13a', '1.20.5-pre3')
|
||||
break
|
||||
case 32:
|
||||
newGameVersions = getRange('1.20.5', '1.20.6')
|
||||
break
|
||||
case 33:
|
||||
newGameVersions = getRange('24w18a', '24w20a')
|
||||
break
|
||||
case 34:
|
||||
newGameVersions = getRange('1.21', '1.21.1')
|
||||
break
|
||||
case 35:
|
||||
newGameVersions.push('24w33a')
|
||||
break
|
||||
case 36:
|
||||
newGameVersions = getRange('24w34a', '24w35a')
|
||||
break
|
||||
case 37:
|
||||
newGameVersions.push('24w36a')
|
||||
break
|
||||
case 38:
|
||||
newGameVersions.push('24w37a')
|
||||
break
|
||||
case 39:
|
||||
newGameVersions = getRange('24w38a', '24w39a')
|
||||
break
|
||||
case 40:
|
||||
newGameVersions.push('24w40a')
|
||||
break
|
||||
case 41:
|
||||
newGameVersions = getRange('1.21.2-pre1', '1.21.2-pre2')
|
||||
break
|
||||
case 42:
|
||||
newGameVersions = getRange('1.21.2', '1.21.3')
|
||||
break
|
||||
case 43:
|
||||
newGameVersions.push('24w44a')
|
||||
break
|
||||
case 44:
|
||||
newGameVersions.push('24w45a')
|
||||
break
|
||||
case 45:
|
||||
newGameVersions.push('24w46a')
|
||||
break
|
||||
case 46:
|
||||
newGameVersions.push('1.21.4')
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loaders,
|
||||
game_versions: newGameVersions,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const zipReader = new JSZip()
|
||||
|
||||
const zip = await zipReader.loadAsync(rawFile)
|
||||
|
||||
for (const fileName in inferFunctions) {
|
||||
const file = zip.file(fileName)
|
||||
|
||||
if (file !== null) {
|
||||
const text = await file.async('text')
|
||||
return inferFunctions[fileName](text, zip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Pack format to Minecraft version mappings
|
||||
// See: https://minecraft.wiki/w/Pack_format
|
||||
|
||||
// NOTE: This needs to be continuously updated as new versions are released.
|
||||
|
||||
// Resource pack format history (full table including development versions)
|
||||
export const RESOURCE_PACK_FORMATS = {
|
||||
1: { min: '1.6.1', max: '1.8.9' },
|
||||
2: { min: '1.9', max: '1.10.2' },
|
||||
3: { min: '1.11', max: '1.12.2' },
|
||||
4: { min: '1.13', max: '1.14.4' },
|
||||
5: { min: '1.15', max: '1.16.1' },
|
||||
6: { min: '1.16.2', max: '1.16.5' },
|
||||
7: { min: '1.17', max: '1.17.1' },
|
||||
8: { min: '1.18', max: '1.18.2' },
|
||||
9: { min: '1.19', max: '1.19.2' },
|
||||
11: { min: '22w42a', max: '22w44a' },
|
||||
12: { min: '1.19.3', max: '1.19.3' },
|
||||
13: { min: '1.19.4', max: '1.19.4' },
|
||||
14: { min: '23w14a', max: '23w16a' },
|
||||
15: { min: '1.20', max: '1.20.1' },
|
||||
16: { min: '23w31a', max: '23w31a' },
|
||||
17: { min: '23w32a', max: '1.20.2-pre1' },
|
||||
18: { min: '1.20.2', max: '1.20.2' },
|
||||
19: { min: '23w42a', max: '23w42a' },
|
||||
20: { min: '23w43a', max: '23w44a' },
|
||||
21: { min: '23w45a', max: '23w46a' },
|
||||
22: { min: '1.20.3', max: '1.20.4' },
|
||||
24: { min: '24w03a', max: '24w04a' },
|
||||
25: { min: '24w05a', max: '24w05b' },
|
||||
26: { min: '24w06a', max: '24w07a' },
|
||||
28: { min: '24w09a', max: '24w10a' },
|
||||
29: { min: '24w11a', max: '24w11a' },
|
||||
30: { min: '24w12a', max: '24w12a' },
|
||||
31: { min: '24w13a', max: '1.20.5-pre3' },
|
||||
32: { min: '1.20.5', max: '1.20.6' },
|
||||
33: { min: '24w18a', max: '24w20a' },
|
||||
34: { min: '1.21', max: '1.21.1' },
|
||||
35: { min: '24w33a', max: '24w33a' },
|
||||
36: { min: '24w34a', max: '24w35a' },
|
||||
37: { min: '24w36a', max: '24w36a' },
|
||||
38: { min: '24w37a', max: '24w37a' },
|
||||
39: { min: '24w38a', max: '24w39a' },
|
||||
40: { min: '24w40a', max: '24w40a' },
|
||||
41: { min: '1.21.2-pre1', max: '1.21.2-pre2' },
|
||||
42: { min: '1.21.2', max: '1.21.3' },
|
||||
43: { min: '24w44a', max: '24w44a' },
|
||||
44: { min: '24w45a', max: '24w45a' },
|
||||
45: { min: '24w46a', max: '24w46a' },
|
||||
46: { min: '1.21.4', max: '1.21.4' },
|
||||
55: { min: '1.21.5', max: '1.21.5' },
|
||||
63: { min: '1.21.6', max: '1.21.6' },
|
||||
64: { min: '1.21.7', max: '1.21.8' },
|
||||
69.0: { min: '1.21.9', max: '1.21.10' },
|
||||
75: { min: '1.21.11', max: '1.21.11' },
|
||||
} as const
|
||||
|
||||
// Data pack format history (full table including development versions)
|
||||
export const DATA_PACK_FORMATS = {
|
||||
4: { min: '1.13', max: '1.14.4' },
|
||||
5: { min: '1.15', max: '1.16.1' },
|
||||
6: { min: '1.16.2', max: '1.16.5' },
|
||||
7: { min: '1.17', max: '1.17.1' },
|
||||
8: { min: '1.18', max: '1.18.1' },
|
||||
9: { min: '1.18.2', max: '1.18.2' },
|
||||
10: { min: '1.19', max: '1.19.3' },
|
||||
11: { min: '23w03a', max: '23w05a' },
|
||||
12: { min: '1.19.4', max: '1.19.4' },
|
||||
13: { min: '23w12a', max: '23w14a' },
|
||||
14: { min: '23w16a', max: '23w17a' },
|
||||
15: { min: '1.20', max: '1.20.1' },
|
||||
16: { min: '23w31a', max: '23w31a' },
|
||||
17: { min: '23w32a', max: '1.20.2-pre1' },
|
||||
18: { min: '1.20.2', max: '1.20.2' },
|
||||
19: { min: '23w40a', max: '23w40a' },
|
||||
20: { min: '23w41a', max: '23w41a' },
|
||||
21: { min: '23w42a', max: '23w42a' },
|
||||
22: { min: '23w43a', max: '23w44a' },
|
||||
23: { min: '23w45a', max: '23w46a' },
|
||||
24: { min: '1.20.3-pre1', max: '1.20.3-pre1' },
|
||||
25: { min: '1.20.3-pre2', max: '1.20.3-pre4' },
|
||||
26: { min: '1.20.3', max: '1.20.4' },
|
||||
27: { min: '23w51a', max: '23w51b' },
|
||||
28: { min: '24w03a', max: '24w04a' },
|
||||
29: { min: '24w05a', max: '24w05b' },
|
||||
30: { min: '24w06a', max: '24w06a' },
|
||||
31: { min: '24w07a', max: '24w07a' },
|
||||
32: { min: '24w09a', max: '24w10a' },
|
||||
33: { min: '24w11a', max: '24w11a' },
|
||||
34: { min: '24w12a', max: '24w12a' },
|
||||
35: { min: '24w13a', max: '24w13a' },
|
||||
36: { min: '24w14a', max: '24w14a' },
|
||||
37: { min: '1.20.5-pre1', max: '1.20.5-pre1' },
|
||||
38: { min: '1.20.5-pre2', max: '1.20.5-pre3' },
|
||||
39: { min: '1.20.5-pre4', max: '1.20.5-rc3' },
|
||||
40: { min: '1.20.5-rc4', max: '1.20.5-rc4' },
|
||||
41: { min: '1.20.5', max: '1.20.6' },
|
||||
42: { min: '24w18a', max: '24w19b' },
|
||||
43: { min: '24w20a', max: '24w20a' },
|
||||
44: { min: '24w21a', max: '24w21b' },
|
||||
45: { min: '1.21-pre1', max: '1.21-pre1' },
|
||||
46: { min: '1.21-pre2', max: '1.21-pre4' },
|
||||
47: { min: '1.21-rc1', max: '1.21-rc1' },
|
||||
48: { min: '1.21', max: '1.21.1' },
|
||||
49: { min: '24w33a', max: '24w33a' },
|
||||
50: { min: '24w34a', max: '24w35a' },
|
||||
51: { min: '24w36a', max: '24w36a' },
|
||||
52: { min: '24w37a', max: '24w37a' },
|
||||
53: { min: '24w38a', max: '24w38a' },
|
||||
54: { min: '24w39a', max: '24w39a' },
|
||||
55: { min: '24w40a', max: '24w40a' },
|
||||
56: { min: '1.21.2-pre1', max: '1.21.2-pre2' },
|
||||
57: { min: '1.21.2', max: '1.21.3' },
|
||||
58: { min: '24w44a', max: '24w44a' },
|
||||
59: { min: '24w45a', max: '24w45a' },
|
||||
60: { min: '24w46a', max: '24w46a' },
|
||||
61: { min: '1.21.4', max: '1.21.4' },
|
||||
71: { min: '1.21.5', max: '1.21.5' },
|
||||
80: { min: '1.21.6', max: '1.21.6' },
|
||||
81: { min: '1.21.7', max: '1.21.8' },
|
||||
88.0: { min: '1.21.9', max: '1.21.10' },
|
||||
94.1: { min: '1.21.11', max: '1.21.11' },
|
||||
} as const
|
||||
@@ -0,0 +1,3 @@
|
||||
export type { InferredVersionInfo } from './infer'
|
||||
export { inferVersionInfo } from './infer'
|
||||
export { extractVersionDetailsFromFilename } from './version-utils'
|
||||
@@ -0,0 +1,132 @@
|
||||
import JSZip from 'jszip'
|
||||
|
||||
import { createLoaderParsers } from './loader-parsers'
|
||||
import { createMultiFileDetectors } from './multi-file-detectors'
|
||||
import { createPackParser } from './pack-parsers'
|
||||
import { extractVersionDetailsFromFilename } from './version-utils'
|
||||
|
||||
export type GameVersion = { version: string; version_type: string }
|
||||
|
||||
export type Project = { title: string; actualProjectType?: string }
|
||||
|
||||
export type RawFile = File | (Blob & { name: string })
|
||||
|
||||
export interface InferredVersionInfo {
|
||||
name?: string
|
||||
version_number?: string
|
||||
version_type?: 'alpha' | 'beta' | 'release'
|
||||
loaders?: string[]
|
||||
game_versions?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in missing version information from the filename if not already present.
|
||||
*/
|
||||
function fillMissingFromFilename(
|
||||
result: InferredVersionInfo,
|
||||
filename: string,
|
||||
projectTitle: string,
|
||||
): InferredVersionInfo {
|
||||
const filenameDetails = extractVersionDetailsFromFilename(filename)
|
||||
|
||||
if (!result.version_number && filenameDetails.versionNumber) {
|
||||
result.version_number = filenameDetails.versionNumber
|
||||
}
|
||||
|
||||
if (!result.version_type) {
|
||||
result.version_type = filenameDetails.versionType
|
||||
}
|
||||
|
||||
if (!result.name && result.version_number) {
|
||||
result.name = `${projectTitle} ${result.version_number}`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to infer version information from a file.
|
||||
* Analyzes mod loaders, packs, and other Minecraft-related file formats.
|
||||
*/
|
||||
export const inferVersionInfo = async function (
|
||||
rawFile: RawFile,
|
||||
project: Project,
|
||||
gameVersions: GameVersion[],
|
||||
): Promise<InferredVersionInfo> {
|
||||
const simplifiedGameVersions = gameVersions
|
||||
.filter((it) => it.version_type === 'release')
|
||||
.map((it) => it.version)
|
||||
|
||||
const zipReader = new JSZip()
|
||||
const zip = await zipReader.loadAsync(rawFile)
|
||||
|
||||
const loaderParsers = createLoaderParsers(project, gameVersions, simplifiedGameVersions)
|
||||
const packParser = createPackParser(project, gameVersions, rawFile)
|
||||
const multiFileDetectors = createMultiFileDetectors(project, gameVersions, rawFile)
|
||||
|
||||
const inferFunctions = {
|
||||
...loaderParsers,
|
||||
'pack.mcmeta': packParser,
|
||||
}
|
||||
|
||||
// Multi-loader detection
|
||||
const multiLoaderFiles = [
|
||||
'META-INF/neoforge.mods.toml',
|
||||
'META-INF/mods.toml',
|
||||
'fabric.mod.json',
|
||||
'quilt.mod.json',
|
||||
]
|
||||
const detectedLoaderFiles = multiLoaderFiles.filter((fileName) => zip.file(fileName) !== null)
|
||||
if (detectedLoaderFiles.length > 1) {
|
||||
const results: InferredVersionInfo[] = []
|
||||
for (const fileName of detectedLoaderFiles) {
|
||||
const file = zip.file(fileName)
|
||||
if (file !== null) {
|
||||
const text = await file.async('text')
|
||||
const parser = inferFunctions[fileName as keyof typeof inferFunctions]
|
||||
if (parser) {
|
||||
const result = await parser(text, zip)
|
||||
if (result && Object.keys(result).length > 0) results.push(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (results.length > 0) {
|
||||
const combinedLoaders = [...new Set(results.flatMap((r) => r.loaders || []))]
|
||||
const allGameVersions = [...new Set(results.flatMap((r) => r.game_versions || []))]
|
||||
const primaryResult = results.find((r) => r.version_number) || results[0]
|
||||
|
||||
const mergedResult = {
|
||||
name: primaryResult.name,
|
||||
version_number: primaryResult.version_number,
|
||||
version_type: primaryResult.version_type,
|
||||
loaders: combinedLoaders,
|
||||
game_versions: allGameVersions,
|
||||
}
|
||||
return fillMissingFromFilename(mergedResult, rawFile.name, project.title)
|
||||
}
|
||||
}
|
||||
|
||||
// Standard single-loader detection
|
||||
for (const fileName in inferFunctions) {
|
||||
const file = zip.file(fileName)
|
||||
|
||||
if (file !== null) {
|
||||
const text = await file.async('text')
|
||||
const parser = inferFunctions[fileName as keyof typeof inferFunctions]
|
||||
if (parser) {
|
||||
const result = await parser(text, zip)
|
||||
return fillMissingFromFilename(result, rawFile.name, project.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-file detection functions
|
||||
for (const detector of Object.values(multiFileDetectors)) {
|
||||
const result = await detector(zip)
|
||||
if (result !== null) {
|
||||
return fillMissingFromFilename(result, rawFile.name, project.title)
|
||||
}
|
||||
}
|
||||
|
||||
return fillMissingFromFilename({}, rawFile.name, project.title)
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import { parse as parseTOML } from '@ltd/j-toml'
|
||||
import yaml from 'js-yaml'
|
||||
import type JSZip from 'jszip'
|
||||
|
||||
import type { GameVersion, InferredVersionInfo, Project } from './infer'
|
||||
import {
|
||||
getGameVersionsMatchingMavenRange,
|
||||
getGameVersionsMatchingSemverRange,
|
||||
} from './version-ranges'
|
||||
import { versionType } from './version-utils'
|
||||
|
||||
/**
|
||||
* Creates the inferFunctions object containing all mod loader parsers.
|
||||
*/
|
||||
export function createLoaderParsers(
|
||||
project: Project,
|
||||
gameVersions: GameVersion[],
|
||||
simplifiedGameVersions: string[],
|
||||
) {
|
||||
return {
|
||||
// NeoForge
|
||||
'META-INF/neoforge.mods.toml': (file: string): InferredVersionInfo => {
|
||||
const metadata = parseTOML(file, { joiner: '\n' }) as any
|
||||
|
||||
const versionNum = metadata.mods?.[0]?.version || ''
|
||||
let newGameVersions: string[] = []
|
||||
|
||||
if (metadata.dependencies) {
|
||||
const neoForgeDependency = Object.values(metadata.dependencies)
|
||||
.flat()
|
||||
.find((dependency: any) => dependency.modId === 'neoforge')
|
||||
|
||||
if (neoForgeDependency) {
|
||||
try {
|
||||
// https://docs.neoforged.net/docs/gettingstarted/versioning/#neoforge
|
||||
const mcVersionRange = (neoForgeDependency as any).versionRange
|
||||
.replace('-beta', '')
|
||||
.replace(
|
||||
/(\d+)(?:\.(\d+))?(?:\.(\d+)?)?/g,
|
||||
(_match: string, major: string, minor: string) => {
|
||||
return `1.${major}${minor ? '.' + minor : ''}`
|
||||
},
|
||||
)
|
||||
newGameVersions = getGameVersionsMatchingMavenRange(
|
||||
mcVersionRange,
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
} catch {
|
||||
// Ignore parsing errors, just leave game_versions empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: versionNum ? `${project.title} ${versionNum}` : '',
|
||||
version_number: versionNum,
|
||||
loaders: ['neoforge'],
|
||||
version_type: versionType(versionNum),
|
||||
game_versions: newGameVersions,
|
||||
}
|
||||
},
|
||||
// Forge 1.13+
|
||||
'META-INF/mods.toml': async (file: string, zip: JSZip): Promise<InferredVersionInfo> => {
|
||||
const metadata = parseTOML(file, { joiner: '\n' }) as any
|
||||
|
||||
if (metadata.mods && metadata.mods.length > 0) {
|
||||
let versionNum = metadata.mods[0].version
|
||||
|
||||
// ${file.jarVersion} -> Implementation-Version from manifest
|
||||
const manifestFile = zip.file('META-INF/MANIFEST.MF')
|
||||
if (metadata.mods[0].version.includes('${file.jarVersion}') && manifestFile !== null) {
|
||||
const manifestText = await manifestFile.async('text')
|
||||
const regex = /Implementation-Version: (.*)$/m
|
||||
const match = manifestText.match(regex)
|
||||
if (match) {
|
||||
versionNum = versionNum.replace('${file.jarVersion}', match[1])
|
||||
}
|
||||
}
|
||||
|
||||
let newGameVersions: string[] = []
|
||||
const mcDependencies = Object.values(metadata.dependencies)
|
||||
.flat()
|
||||
.filter((dependency: any) => dependency.modId === 'minecraft')
|
||||
|
||||
if (mcDependencies.length > 0) {
|
||||
newGameVersions = getGameVersionsMatchingMavenRange(
|
||||
(mcDependencies[0] as any).versionRange,
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title} ${versionNum}`,
|
||||
version_number: versionNum,
|
||||
version_type: versionType(versionNum),
|
||||
loaders: ['forge'],
|
||||
game_versions: newGameVersions,
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// Old Forge
|
||||
'mcmod.info': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
return {
|
||||
name: metadata.version ? `${project.title} ${metadata.version}` : '',
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['forge'],
|
||||
game_versions: simplifiedGameVersions.filter((version) =>
|
||||
version.startsWith(metadata.mcversion),
|
||||
),
|
||||
}
|
||||
},
|
||||
// Fabric
|
||||
'fabric.mod.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
const detectedGameVersions = metadata.depends
|
||||
? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions)
|
||||
: []
|
||||
const loaders: string[] = []
|
||||
|
||||
// Detect 1.3-1.13 -> legacy-fabric
|
||||
const hasLegacyVersions = detectedGameVersions.some((version) => {
|
||||
const match = version.match(/^1\.(\d+)/)
|
||||
return match && parseInt(match[1]) >= 3 && parseInt(match[1]) <= 13
|
||||
})
|
||||
|
||||
if (hasLegacyVersions) loaders.push('legacy-fabric')
|
||||
else loaders.push('fabric')
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
loaders,
|
||||
version_type: versionType(metadata.version),
|
||||
game_versions: detectedGameVersions,
|
||||
}
|
||||
},
|
||||
// Quilt
|
||||
'quilt.mod.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.quilt_loader.version}`,
|
||||
version_number: metadata.quilt_loader.version,
|
||||
loaders: ['quilt'],
|
||||
version_type: versionType(metadata.quilt_loader.version),
|
||||
game_versions: metadata.quilt_loader.depends
|
||||
? getGameVersionsMatchingSemverRange(
|
||||
metadata.quilt_loader.depends.find((x: any) => x.id === 'minecraft')
|
||||
? metadata.quilt_loader.depends.find((x: any) => x.id === 'minecraft').versions
|
||||
: [],
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
: [],
|
||||
}
|
||||
},
|
||||
// Bukkit + Other Forks
|
||||
'plugin.yml': (file: string): InferredVersionInfo => {
|
||||
const metadata = yaml.load(file) as any
|
||||
|
||||
// Check for Folia support
|
||||
const loaders = []
|
||||
if (metadata['folia-supported'] === true) {
|
||||
loaders.push('folia')
|
||||
}
|
||||
// We don't know which fork of Bukkit users are using otherwise
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders,
|
||||
game_versions: gameVersions
|
||||
.filter(
|
||||
(x) => x.version.startsWith(metadata['api-version']) && x.version_type === 'release',
|
||||
)
|
||||
.map((x) => x.version),
|
||||
}
|
||||
},
|
||||
// Paper 1.19.3+
|
||||
'paper-plugin.yml': (file: string): InferredVersionInfo => {
|
||||
const metadata = yaml.load(file) as any
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['paper'],
|
||||
game_versions: gameVersions
|
||||
.filter(
|
||||
(x) => x.version.startsWith(metadata['api-version']) && x.version_type === 'release',
|
||||
)
|
||||
.map((x) => x.version),
|
||||
}
|
||||
},
|
||||
// Bungeecord + Waterfall
|
||||
'bungee.yml': (file: string): InferredVersionInfo => {
|
||||
const metadata = yaml.load(file) as any
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['bungeecord'],
|
||||
}
|
||||
},
|
||||
// Velocity
|
||||
'velocity-plugin.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.version}`,
|
||||
version_number: metadata.version,
|
||||
version_type: versionType(metadata.version),
|
||||
loaders: ['velocity'],
|
||||
}
|
||||
},
|
||||
// Sponge plugin (8+)
|
||||
'META-INF/sponge_plugins.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
const plugin = metadata.plugins?.[0]
|
||||
|
||||
if (!plugin) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
name: plugin.version ? `${project.title} ${plugin.version}` : '',
|
||||
version_number: plugin.version,
|
||||
version_type: versionType(plugin.version),
|
||||
loaders: ['sponge'],
|
||||
}
|
||||
},
|
||||
// Modpacks
|
||||
'modrinth.index.json': (file: string): InferredVersionInfo => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
const loaders = []
|
||||
if ('forge' in metadata.dependencies) {
|
||||
loaders.push('forge')
|
||||
}
|
||||
if ('neoforge' in metadata.dependencies) {
|
||||
loaders.push('neoforge')
|
||||
}
|
||||
if ('fabric-loader' in metadata.dependencies) {
|
||||
loaders.push('fabric')
|
||||
}
|
||||
if ('quilt-loader' in metadata.dependencies) {
|
||||
loaders.push('quilt')
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title} ${metadata.versionId}`,
|
||||
version_number: metadata.versionId,
|
||||
version_type: versionType(metadata.versionId),
|
||||
loaders,
|
||||
game_versions: gameVersions
|
||||
.filter((x) => x.version === metadata.dependencies.minecraft)
|
||||
.map((x) => x.version),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import type JSZip from 'jszip'
|
||||
|
||||
import type { GameVersion, InferredVersionInfo, Project, RawFile } from './infer'
|
||||
import { extractVersionFromFilename, versionType } from './version-utils'
|
||||
|
||||
/**
|
||||
* Creates multi-file detection functions that scan multiple files in a zip.
|
||||
*/
|
||||
export function createMultiFileDetectors(
|
||||
project: Project,
|
||||
gameVersions: GameVersion[],
|
||||
rawFile: RawFile,
|
||||
) {
|
||||
return {
|
||||
// Legacy texture pack (pre-1.6.1)
|
||||
legacyTexturePack: async (zip: JSZip): Promise<InferredVersionInfo | null> => {
|
||||
const packTxt = zip.file('pack.txt')
|
||||
if (!packTxt) return null
|
||||
|
||||
// Check for legacy texture pack files/directories
|
||||
const legacyIndicators = [
|
||||
'font.txt',
|
||||
'particles.png',
|
||||
'achievement/',
|
||||
'armor/',
|
||||
'art/',
|
||||
'environment/',
|
||||
'font/',
|
||||
'gui/',
|
||||
'item/',
|
||||
'lang/',
|
||||
'misc/',
|
||||
'mob/',
|
||||
'textures/',
|
||||
'title/',
|
||||
]
|
||||
|
||||
const hasLegacyContent = legacyIndicators.some((indicator) => {
|
||||
if (indicator.endsWith('/')) {
|
||||
return zip.file(new RegExp(`^${indicator}`))?.length > 0
|
||||
}
|
||||
return zip.file(indicator) !== null
|
||||
})
|
||||
|
||||
if (!hasLegacyContent) return null
|
||||
|
||||
// Legacy texture packs are compatible with a1.2.2 to 1.5.2
|
||||
// We'll return versions from 1.0 to 1.5.2 (as older alpha/beta versions may not be in gameVersions)
|
||||
const legacyVersions = gameVersions
|
||||
.filter((v) => {
|
||||
const version = v.version
|
||||
// Match 1.0 through 1.5.2
|
||||
if (version.match(/^1\.[0-4](\.\d+)?$/) || version.match(/^1\.5(\.[0-2])?$/)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
.map((v) => v.version)
|
||||
|
||||
const versionNum = extractVersionFromFilename(rawFile.name)
|
||||
|
||||
return {
|
||||
name: versionNum ? `${project.title} ${versionNum}` : undefined,
|
||||
version_number: versionNum || undefined,
|
||||
version_type: versionType(versionNum),
|
||||
loaders: ['minecraft'],
|
||||
game_versions: legacyVersions,
|
||||
}
|
||||
},
|
||||
|
||||
// Shader pack (OptiFine/Iris)
|
||||
shaderPack: async (zip: JSZip): Promise<InferredVersionInfo | null> => {
|
||||
const shadersDir = zip.file(/^shaders\//)
|
||||
if (!shadersDir || shadersDir.length === 0) return null
|
||||
|
||||
const loaders: string[] = []
|
||||
|
||||
// Check for Iris-specific features in shaders.properties
|
||||
const shaderProps = zip.file('shaders/shaders.properties')
|
||||
if (shaderProps) {
|
||||
const propsText = await shaderProps.async('text')
|
||||
if (
|
||||
propsText.includes('iris.features.required') ||
|
||||
propsText.includes('iris.features.optional')
|
||||
) {
|
||||
loaders.push('iris', 'optifine')
|
||||
}
|
||||
}
|
||||
|
||||
// If no specific loader detected, it could be OptiFine or Iris
|
||||
if (loaders.length === 0) {
|
||||
loaders.push('optifine', 'iris')
|
||||
}
|
||||
|
||||
const versionNum = extractVersionFromFilename(rawFile.name)
|
||||
|
||||
return {
|
||||
name: versionNum ? `${project.title} ${versionNum}` : undefined,
|
||||
version_number: versionNum || undefined,
|
||||
version_type: versionType(versionNum),
|
||||
loaders,
|
||||
game_versions: [],
|
||||
}
|
||||
},
|
||||
|
||||
// NilLoader mod
|
||||
nilLoaderMod: async (zip: JSZip): Promise<InferredVersionInfo | null> => {
|
||||
const nilModFiles = zip.file(/\.nilmod\.css$/)
|
||||
if (!nilModFiles || nilModFiles.length === 0) return null
|
||||
|
||||
return {
|
||||
loaders: ['nilloader'],
|
||||
game_versions: [],
|
||||
}
|
||||
},
|
||||
|
||||
// Java Agent
|
||||
javaAgent: async (zip: JSZip): Promise<InferredVersionInfo | null> => {
|
||||
const manifest = zip.file('META-INF/MANIFEST.MF')
|
||||
if (!manifest) return null
|
||||
|
||||
const manifestText = await manifest.async('text')
|
||||
if (!manifestText.includes('Premain-Class:')) return null
|
||||
|
||||
return {
|
||||
loaders: ['java-agent'],
|
||||
game_versions: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import type JSZip from 'jszip'
|
||||
|
||||
import { DATA_PACK_FORMATS, RESOURCE_PACK_FORMATS } from './constants'
|
||||
import type { GameVersion, InferredVersionInfo, Project, RawFile } from './infer'
|
||||
import { extractVersionFromFilename, versionType } from './version-utils'
|
||||
|
||||
type PackFormat = number | [number] | [number, number]
|
||||
|
||||
/**
|
||||
* Normalizes a pack format to [major, minor] tuple. See https://minecraft.wiki/w/Pack.mcmeta
|
||||
* - Single integer: [major, 0] for min, [major, Infinity] for max
|
||||
* - Array [major]: [major, 0] for min, [major, Infinity] for max
|
||||
* - Array [major, minor]: returns as-is
|
||||
*/
|
||||
function normalizePackFormat(format: PackFormat, isMax: boolean): [number, number] {
|
||||
if (Array.isArray(format)) {
|
||||
if (format.length === 1) {
|
||||
return isMax ? [format[0], Infinity] : [format[0], 0]
|
||||
}
|
||||
return [format[0], format[1]]
|
||||
}
|
||||
return isMax ? [format, Infinity] : [format, 0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two pack formats [major, minor].
|
||||
* Returns: -1 if a < b, 0 if equal, 1 if a > b
|
||||
*/
|
||||
function comparePackFormats(a: [number, number], b: [number, number]): number {
|
||||
if (a[0] !== b[0]) return a[0] - b[0]
|
||||
return a[1] - b[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a format number falls within the min/max range.
|
||||
*/
|
||||
function isFormatInRange(
|
||||
format: number,
|
||||
minFormat: [number, number],
|
||||
maxFormat: [number, number],
|
||||
): boolean {
|
||||
// Check if the major version matches
|
||||
if (format < minFormat[0] || format > maxFormat[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If major version is exactly min or max, we need to check minor version
|
||||
// For entries in our map, we treat them as [major, 0]
|
||||
const formatTuple: [number, number] = [format, 0]
|
||||
|
||||
// If the format has a decimal (like 69.0, 88.0), extract it
|
||||
const formatStr = format.toString()
|
||||
if (formatStr.includes('.')) {
|
||||
const [maj, min] = formatStr.split('.').map(Number)
|
||||
formatTuple[0] = maj
|
||||
formatTuple[1] = min
|
||||
}
|
||||
|
||||
return (
|
||||
comparePackFormats(formatTuple, minFormat) >= 0 &&
|
||||
comparePackFormats(formatTuple, maxFormat) <= 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a range of game versions between two versions.
|
||||
*/
|
||||
function getRange(versionA: string, versionB: string, gameVersions: GameVersion[]): string[] {
|
||||
const startingIndex = gameVersions.findIndex((x) => x.version === versionA)
|
||||
const endingIndex = gameVersions.findIndex((x) => x.version === versionB)
|
||||
|
||||
if (startingIndex === -1 || endingIndex === -1) {
|
||||
return []
|
||||
}
|
||||
|
||||
const final = []
|
||||
const filterOnlyRelease = gameVersions[startingIndex]?.version_type === 'release'
|
||||
|
||||
for (let i = startingIndex; i >= endingIndex; i--) {
|
||||
if (gameVersions[i].version_type === 'release' || !filterOnlyRelease) {
|
||||
final.push(gameVersions[i].version)
|
||||
}
|
||||
}
|
||||
|
||||
return final
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets game versions from a single pack format number.
|
||||
*/
|
||||
function getVersionsFromPackFormat(
|
||||
packFormat: number,
|
||||
formatMap: Record<number, { min: string; max: string }>,
|
||||
gameVersions: GameVersion[],
|
||||
): string[] {
|
||||
const mapping = formatMap[packFormat]
|
||||
if (!mapping) {
|
||||
return []
|
||||
}
|
||||
return getRange(mapping.min, mapping.max, gameVersions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets game versions from a pack format range (min to max inclusive).
|
||||
* Supports both integer and [major, minor] format specifications.
|
||||
*/
|
||||
function getVersionsFromFormatRange(
|
||||
minFormat: PackFormat,
|
||||
maxFormat: PackFormat,
|
||||
formatMap: Record<number, { min: string; max: string }>,
|
||||
gameVersions: GameVersion[],
|
||||
): string[] {
|
||||
const normalizedMin = normalizePackFormat(minFormat, false)
|
||||
const normalizedMax = normalizePackFormat(maxFormat, true)
|
||||
|
||||
// Get all format numbers from the map that fall within the range
|
||||
const allVersions: string[] = []
|
||||
const formatNumbers = Object.keys(formatMap)
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b)
|
||||
|
||||
for (const format of formatNumbers) {
|
||||
if (isFormatInRange(format, normalizedMin, normalizedMax)) {
|
||||
const versions = getVersionsFromPackFormat(format, formatMap, gameVersions)
|
||||
for (const version of versions) {
|
||||
if (!allVersions.includes(version)) {
|
||||
allVersions.push(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allVersions
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets game versions from pack.mcmeta metadata.
|
||||
* Supports multiple format specifications:
|
||||
* - min_format + max_format: Can be integers or [major, minor] arrays (since 25w31a)
|
||||
* - supported_formats: Single int, array of ints, or { min_inclusive, max_inclusive }
|
||||
* - pack_format: Single format number (legacy)
|
||||
*/
|
||||
function getGameVersionsFromPackMeta(
|
||||
packMeta: any,
|
||||
formatMap: Record<number, { min: string; max: string }>,
|
||||
gameVersions: GameVersion[],
|
||||
): string[] {
|
||||
const pack = packMeta.pack
|
||||
if (!pack) return []
|
||||
|
||||
// Check for min_format and max_format (25w31a+ format)
|
||||
// These can be: int (e.g., 82), [int] (e.g., [82]), or [major, minor] (e.g., [88, 0])
|
||||
if (pack.min_format !== undefined && pack.max_format !== undefined) {
|
||||
return getVersionsFromFormatRange(pack.min_format, pack.max_format, formatMap, gameVersions)
|
||||
}
|
||||
|
||||
// Check for supported_formats
|
||||
if (pack.supported_formats !== undefined) {
|
||||
const formats = pack.supported_formats
|
||||
|
||||
// Single integer: major version
|
||||
if (typeof formats === 'number') {
|
||||
return getVersionsFromPackFormat(formats, formatMap, gameVersions)
|
||||
}
|
||||
|
||||
// Array of integers or [min, max] range
|
||||
if (Array.isArray(formats)) {
|
||||
if (
|
||||
formats.length === 2 &&
|
||||
typeof formats[0] === 'number' &&
|
||||
typeof formats[1] === 'number'
|
||||
) {
|
||||
// Could be [major, minor] or [minMajor, maxMajor]
|
||||
// Based on context, if both are close (within ~50), treat as major version range
|
||||
// Otherwise, treat as [major, minor]
|
||||
if (Math.abs(formats[1] - formats[0]) < 50) {
|
||||
// Likely a major version range like [42, 45]
|
||||
return getVersionsFromFormatRange(formats[0], formats[1], formatMap, gameVersions)
|
||||
}
|
||||
}
|
||||
|
||||
// Array of major versions
|
||||
const allVersions: string[] = []
|
||||
for (const format of formats) {
|
||||
if (typeof format === 'number') {
|
||||
const versions = getVersionsFromPackFormat(format, formatMap, gameVersions)
|
||||
for (const version of versions) {
|
||||
if (!allVersions.includes(version)) {
|
||||
allVersions.push(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return allVersions
|
||||
}
|
||||
|
||||
// Object format: { min_inclusive, max_inclusive }
|
||||
if (
|
||||
typeof formats === 'object' &&
|
||||
formats.min_inclusive !== undefined &&
|
||||
formats.max_inclusive !== undefined
|
||||
) {
|
||||
return getVersionsFromFormatRange(
|
||||
formats.min_inclusive,
|
||||
formats.max_inclusive,
|
||||
formatMap,
|
||||
gameVersions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to pack_format (legacy single format)
|
||||
if (pack.pack_format !== undefined) {
|
||||
return getVersionsFromPackFormat(pack.pack_format, formatMap, gameVersions)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the pack.mcmeta parser function.
|
||||
*/
|
||||
export function createPackParser(project: Project, gameVersions: GameVersion[], rawFile: RawFile) {
|
||||
return async (file: string, zip: JSZip): Promise<InferredVersionInfo> => {
|
||||
const metadata = JSON.parse(file) as any
|
||||
|
||||
// Check for assets/ directory (resource pack) or data/ directory (data pack)
|
||||
const hasAssetsDir = zip.file(/^assets\//)?.[0] !== undefined
|
||||
const hasDataDir = zip.file(/^data\//)?.[0] !== undefined
|
||||
const hasZipExtension = rawFile.name.toLowerCase().endsWith('.zip')
|
||||
|
||||
const loaders: string[] = []
|
||||
let newGameVersions: string[] = []
|
||||
|
||||
// Data pack detection: has data/ directory
|
||||
if (hasDataDir && hasZipExtension) {
|
||||
loaders.push('datapack')
|
||||
newGameVersions = getGameVersionsFromPackMeta(metadata, DATA_PACK_FORMATS, gameVersions)
|
||||
}
|
||||
// Resource pack detection: has assets/ directory
|
||||
else if (hasAssetsDir && hasZipExtension) {
|
||||
loaders.push('minecraft')
|
||||
newGameVersions = getGameVersionsFromPackMeta(metadata, RESOURCE_PACK_FORMATS, gameVersions)
|
||||
}
|
||||
|
||||
// Fallback to old behavior based on project type
|
||||
else if (project.actualProjectType === 'mod') {
|
||||
loaders.push('datapack')
|
||||
newGameVersions = getGameVersionsFromPackMeta(metadata, DATA_PACK_FORMATS, gameVersions)
|
||||
} else if (project.actualProjectType === 'resourcepack') {
|
||||
loaders.push('minecraft')
|
||||
newGameVersions = getGameVersionsFromPackMeta(metadata, RESOURCE_PACK_FORMATS, gameVersions)
|
||||
}
|
||||
|
||||
// Try to extract version from filename
|
||||
const versionNum = extractVersionFromFilename(rawFile.name)
|
||||
|
||||
return {
|
||||
name: versionNum ? `${project.title} ${versionNum}` : undefined,
|
||||
version_number: versionNum || undefined,
|
||||
version_type: versionType(versionNum),
|
||||
loaders,
|
||||
game_versions: newGameVersions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { satisfies } from 'semver'
|
||||
|
||||
/**
|
||||
* Returns game versions that match a semver range or array of ranges.
|
||||
*/
|
||||
export function getGameVersionsMatchingSemverRange(
|
||||
range: string | string[] | undefined,
|
||||
gameVersions: string[],
|
||||
): string[] {
|
||||
if (!range) {
|
||||
return []
|
||||
}
|
||||
const ranges = Array.isArray(range) ? range : [range]
|
||||
// Normalize ranges: strip trailing hyphens from version numbers used by Fabric for prerelease matching (e.g., ">=1.21.11-" -> ">=1.21.11")
|
||||
const normalizedRanges = ranges.map((r) => r.replace(/(\d)-(\s|$)/g, '$1$2'))
|
||||
return gameVersions.filter((version) => {
|
||||
const semverVersion = version.split('.').length === 2 ? `${version}.0` : version // add patch version if missing (e.g. 1.16 -> 1.16.0)
|
||||
return normalizedRanges.some((v) => satisfies(semverVersion, v))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns game versions that match a Maven-style version range.
|
||||
*/
|
||||
export function getGameVersionsMatchingMavenRange(
|
||||
range: string | undefined,
|
||||
gameVersions: string[],
|
||||
): string[] {
|
||||
if (!range) {
|
||||
return []
|
||||
}
|
||||
const ranges = []
|
||||
|
||||
while (range.startsWith('[') || range.startsWith('(')) {
|
||||
let index = range.indexOf(')')
|
||||
const index2 = range.indexOf(']')
|
||||
if (index === -1 || (index2 !== -1 && index2 < index)) {
|
||||
index = index2
|
||||
}
|
||||
if (index === -1) break
|
||||
ranges.push(range.substring(0, index + 1))
|
||||
range = range.substring(index + 1).trim()
|
||||
if (range.startsWith(',')) {
|
||||
range = range.substring(1).trim()
|
||||
}
|
||||
}
|
||||
|
||||
if (range) {
|
||||
ranges.push(range)
|
||||
}
|
||||
|
||||
const LESS_THAN_EQUAL = /^\(,(.*)]$/
|
||||
const LESS_THAN = /^\(,(.*)\)$/
|
||||
const EQUAL = /^\[(.*)]$/
|
||||
const GREATER_THAN_EQUAL = /^\[(.*),\)$/
|
||||
const GREATER_THAN = /^\((.*),\)$/
|
||||
const BETWEEN = /^\((.*),(.*)\)$/
|
||||
const BETWEEN_EQUAL = /^\[(.*),(.*)]$/
|
||||
const BETWEEN_LESS_THAN_EQUAL = /^\((.*),(.*)]$/
|
||||
const BETWEEN_GREATER_THAN_EQUAL = /^\[(.*),(.*)\)$/
|
||||
|
||||
const semverRanges = []
|
||||
|
||||
for (const range of ranges) {
|
||||
let result
|
||||
if ((result = range.match(LESS_THAN_EQUAL))) {
|
||||
semverRanges.push(`<=${result[1]}`)
|
||||
} else if ((result = range.match(LESS_THAN))) {
|
||||
semverRanges.push(`<${result[1]}`)
|
||||
} else if ((result = range.match(EQUAL))) {
|
||||
semverRanges.push(`${result[1]}`)
|
||||
} else if ((result = range.match(GREATER_THAN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]}`)
|
||||
} else if ((result = range.match(GREATER_THAN))) {
|
||||
semverRanges.push(`>${result[1]}`)
|
||||
} else if ((result = range.match(BETWEEN))) {
|
||||
semverRanges.push(`>${result[1]} <${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]} <=${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_LESS_THAN_EQUAL))) {
|
||||
semverRanges.push(`>${result[1]} <=${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_GREATER_THAN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]} <${result[2]}`)
|
||||
}
|
||||
}
|
||||
return getGameVersionsMatchingSemverRange(semverRanges, gameVersions)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Determines the version type based on the version string.
|
||||
*/
|
||||
export function versionType(number: string | null | undefined): 'alpha' | 'beta' | 'release' {
|
||||
if (!number) return 'release'
|
||||
if (number.includes('alpha')) {
|
||||
return 'alpha'
|
||||
} else if (
|
||||
number.includes('beta') ||
|
||||
number.match(/[^A-z](rc)[^A-z]/) || // includes `rc`
|
||||
number.match(/[^A-z](pre)[^A-z]/) // includes `pre`
|
||||
) {
|
||||
return 'beta'
|
||||
} else {
|
||||
return 'release'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts version number from a filename.
|
||||
*/
|
||||
export function extractVersionFromFilename(filename: string | null | undefined): string | null {
|
||||
if (!filename) return null
|
||||
|
||||
// Remove file extension
|
||||
let baseName = filename.replace(/\.(zip|jar)$/i, '')
|
||||
|
||||
// Remove explicit MC version markers: mc followed by version (e.g., +mc1.21.11, -mc1.21, _mc1.21.4)
|
||||
baseName = baseName.replace(/[+_-]mc\d+\.\d+(?:\.\d+)?/gi, '')
|
||||
|
||||
const versionPatterns = [
|
||||
/[_\-\s]v(\d+(?:\.\d+)*)/i, // Match version with 'v' anywhere: "Name-v1.2.3-extra" (less strict)
|
||||
/[_\-\s]r(\d+(?:\.\d+)*)/i, // Match version with 'r' anywhere: "Name-r1.2.3-extra" (less strict)
|
||||
/[_\-\s](\d+(?:\.\d+)+)$/, // Match version at end after space/separator: "Name 1.2.3"
|
||||
/(\d+\.\d+(?:\.\d+)*)/, // Match any version pattern x.x or x.x.x.x...: "Name1.2.3extra"
|
||||
]
|
||||
|
||||
for (const pattern of versionPatterns) {
|
||||
const match = baseName.match(pattern)
|
||||
if (match && match[1]) {
|
||||
return match[1]
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts version details from a filename (public API).
|
||||
*/
|
||||
export function extractVersionDetailsFromFilename(filename: string | null | undefined) {
|
||||
const versionNum = extractVersionFromFilename(filename)
|
||||
return {
|
||||
versionNumber: versionNum || undefined,
|
||||
versionType: versionType(versionNum),
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@
|
||||
route.path !== '/settings/billing'
|
||||
"
|
||||
/>
|
||||
<PreviewBanner v-if="config.public.buildEnv === 'production' && config.public.preview" />
|
||||
<StagingBanner v-if="config.public.apiBaseUrl.startsWith('https://staging-api.modrinth.com')" />
|
||||
<GeneratedStateErrorsBanner
|
||||
:errors="generatedStateErrors"
|
||||
@@ -339,6 +340,12 @@
|
||||
link: '/admin/servers/notices',
|
||||
shown: isAdmin(auth.user),
|
||||
},
|
||||
{
|
||||
id: 'servers-transfers',
|
||||
color: 'primary',
|
||||
link: '/admin/servers/transfers',
|
||||
shown: isAdmin(auth.user),
|
||||
},
|
||||
{
|
||||
id: 'servers-nodes',
|
||||
color: 'primary',
|
||||
@@ -367,6 +374,9 @@
|
||||
<template #servers-notices>
|
||||
<IssuesIcon aria-hidden="true" /> {{ formatMessage(messages.manageServerNotices) }}
|
||||
</template>
|
||||
<template #servers-transfers>
|
||||
<TransferIcon aria-hidden="true" /> Server transfers
|
||||
</template>
|
||||
<template #affiliates>
|
||||
<AffiliateIcon aria-hidden="true" /> {{ formatMessage(messages.manageAffiliates) }}
|
||||
</template>
|
||||
@@ -695,6 +705,7 @@ import {
|
||||
SettingsIcon,
|
||||
ShieldAlertIcon,
|
||||
SunIcon,
|
||||
TransferIcon,
|
||||
UserIcon,
|
||||
UserSearchIcon,
|
||||
XIcon,
|
||||
@@ -713,6 +724,7 @@ import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
|
||||
import TextLogo from '~/components/brand/TextLogo.vue'
|
||||
import BatchCreditModal from '~/components/ui/admin/BatchCreditModal.vue'
|
||||
import GeneratedStateErrorsBanner from '~/components/ui/banner/GeneratedStateErrorsBanner.vue'
|
||||
import PreviewBanner from '~/components/ui/banner/PreviewBanner.vue'
|
||||
import RussiaBanner from '~/components/ui/banner/RussiaBanner.vue'
|
||||
import StagingBanner from '~/components/ui/banner/StagingBanner.vue'
|
||||
import SubscriptionPaymentFailedBanner from '~/components/ui/banner/SubscriptionPaymentFailedBanner.vue'
|
||||
@@ -742,9 +754,10 @@ const route = useNativeRoute()
|
||||
const router = useNativeRouter()
|
||||
const link = config.public.siteUrl + route.path.replace(/\/+$/, '')
|
||||
|
||||
const { data: payoutBalance } = await useAsyncData('payout/balance', () =>
|
||||
useBaseFetch('payout/balance', { apiVersion: 3 }),
|
||||
)
|
||||
const { data: payoutBalance } = await useAsyncData('payout/balance', () => {
|
||||
if (!auth.value.user) return null
|
||||
return useBaseFetch('payout/balance', { apiVersion: 3 })
|
||||
})
|
||||
|
||||
const showTaxComplianceBanner = computed(() => {
|
||||
if (flags.value.testTaxForm && auth.value.user) return true
|
||||
@@ -977,16 +990,19 @@ const navRoutes = computed(() => [
|
||||
])
|
||||
|
||||
const userMenuOptions = computed(() => {
|
||||
const user = auth.value.user
|
||||
if (!user) return []
|
||||
|
||||
let options = [
|
||||
{
|
||||
id: 'profile',
|
||||
link: `/user/${auth.value.user.username}`,
|
||||
link: `/user/${user.username}`,
|
||||
},
|
||||
{
|
||||
id: 'plus',
|
||||
link: '/plus',
|
||||
color: 'purple',
|
||||
shown: !flags.value.hidePlusPromoInUserMenu && !isPermission(auth.value.user.badges, 1 << 0),
|
||||
shown: !flags.value.hidePlusPromoInUserMenu && !isPermission(user.badges, 1 << 0),
|
||||
},
|
||||
{
|
||||
id: 'servers',
|
||||
@@ -1039,7 +1055,7 @@ const userMenuOptions = computed(() => {
|
||||
{
|
||||
id: 'affiliate-links',
|
||||
link: '/dashboard/affiliate-links',
|
||||
shown: auth.value.user.badges & UserBadge.AFFILIATE,
|
||||
shown: user.badges & UserBadge.AFFILIATE,
|
||||
},
|
||||
{
|
||||
id: 'revenue',
|
||||
|
||||
@@ -368,9 +368,18 @@
|
||||
"collection.button.edit-icon": {
|
||||
"message": "تعديل الأيقونة"
|
||||
},
|
||||
"collection.button.remove-icon": {
|
||||
"message": "حذف الأيقونة"
|
||||
},
|
||||
"collection.button.remove-project": {
|
||||
"message": "إزالة المشروع"
|
||||
},
|
||||
"collection.button.replace-icon": {
|
||||
"message": "تغيير الأيقونة"
|
||||
},
|
||||
"collection.button.select-icon": {
|
||||
"message": "تحديد الأيقونة"
|
||||
},
|
||||
"collection.button.unfollow-project": {
|
||||
"message": "إلغاء متابعة المشروع"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"displayName": {
|
||||
"description": "Please enter the name of the language in its specific variant or regional form (e.g., English (US) for American English, not just English). If the language does not have any specific variant, simply enter the name of the language (e.g., Français, Deutsch).",
|
||||
"message": "الإنجليزية (الولايات المتحدة)"
|
||||
"message": "العربية (المملكة العربية السعودية)"
|
||||
},
|
||||
"searchTerms": {
|
||||
"description": "Please provide additional search terms associated with the language, if needed, to enhance the search functionality (e.g., American English, Deutschland). Each search term should be entered on a separate line. Translate as a hyphen (-) if no additional terms are needed.",
|
||||
"message": "الولايات المتحدة الأمريكية\nالإنجليزية الأمريكية"
|
||||
"message": "المملكة العربية السعودية \nالعربية السعودية"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +590,9 @@
|
||||
"dashboard.collections.long-title": {
|
||||
"message": "Vaše kolekce"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.title": {
|
||||
"message": "Hotovo! 🎉"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.us-citizen.question": {
|
||||
"message": "Jste občan USA?"
|
||||
},
|
||||
@@ -599,6 +602,12 @@
|
||||
"dashboard.creator-withdraw-modal.method-selection.country-placeholder": {
|
||||
"message": "Zvolte vaši zemi"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.method-selection.country-search-placeholder": {
|
||||
"message": "Vyhladaj štát..."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.method-selection.region": {
|
||||
"message": "Región"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.account-owner": {
|
||||
"message": "Majitel účtu"
|
||||
},
|
||||
|
||||
@@ -191,6 +191,9 @@
|
||||
"app-marketing.hero.download-modrinth-app-for-os": {
|
||||
"message": "Hent Modrinth Appen til {os}"
|
||||
},
|
||||
"app-marketing.hero.more-download-options": {
|
||||
"message": "Flere Download Muligheder"
|
||||
},
|
||||
"auth.authorize.action.authorize": {
|
||||
"message": "Godkend"
|
||||
},
|
||||
@@ -287,6 +290,66 @@
|
||||
"auth.sign-up.label.username": {
|
||||
"message": "Brugernavn"
|
||||
},
|
||||
"auth.sign-up.legal-dislaimer": {
|
||||
"message": "Ved at oprette en konto, accepterer du Modrinth's <terms-link>Vilkår</terms-link> og <privacy-policy-link>Privatlivspolitik</privacy-policy-link>."
|
||||
},
|
||||
"auth.sign-up.notification.password-mismatch.text": {
|
||||
"message": "Adgangskoderne er ikke ens!"
|
||||
},
|
||||
"auth.sign-up.password.label": {
|
||||
"message": "Adgangskode"
|
||||
},
|
||||
"auth.sign-up.sign-in-option.title": {
|
||||
"message": "Har du allerede en konto?"
|
||||
},
|
||||
"auth.sign-up.subscribe.label": {
|
||||
"message": "Abonner på opdateringer om Modrinth"
|
||||
},
|
||||
"auth.sign-up.title": {
|
||||
"message": "Tilmeld"
|
||||
},
|
||||
"auth.sign-up.title.create-account": {
|
||||
"message": "Eller opret en konto selv"
|
||||
},
|
||||
"auth.sign-up.title.sign-up-with": {
|
||||
"message": "Tilmeld dig med"
|
||||
},
|
||||
"auth.verify-email.action.account-settings": {
|
||||
"message": "Kontoindstillinger"
|
||||
},
|
||||
"auth.verify-email.action.sign-in": {
|
||||
"message": "Log ind"
|
||||
},
|
||||
"auth.verify-email.already-verified.description": {
|
||||
"message": "Din e-mail er allerede bekræftet!"
|
||||
},
|
||||
"auth.verify-email.already-verified.title": {
|
||||
"message": "E-mail allerede bekræftet"
|
||||
},
|
||||
"auth.verify-email.failed-verification.action": {
|
||||
"message": "Send bekræftelsesmail igen"
|
||||
},
|
||||
"auth.verify-email.failed-verification.description": {
|
||||
"message": "Vi kunne ikke bekræfte din e-mail. Prøv at sende bekræftelsesmailen igen via dit dashboard ved at logge ind."
|
||||
},
|
||||
"auth.verify-email.failed-verification.description.logged-in": {
|
||||
"message": "Vi kunne ikke bekræfte din e-mail. Prøv at sende bekræftelsesmailen igen via knappen nedenfor."
|
||||
},
|
||||
"auth.verify-email.failed-verification.title": {
|
||||
"message": "E-mailbekræftelse mislykkedes"
|
||||
},
|
||||
"auth.verify-email.post-verification.description": {
|
||||
"message": "Din e-mailadresse er blevet bekræftet!"
|
||||
},
|
||||
"auth.verify-email.post-verification.title": {
|
||||
"message": "E-mailbekræftelse"
|
||||
},
|
||||
"auth.verify-email.title": {
|
||||
"message": "Bekræft E-mail"
|
||||
},
|
||||
"auth.welcome.checkbox.subscribe": {
|
||||
"message": "Abonner på opdateringer om Modrinth"
|
||||
},
|
||||
"collection.button.remove-project": {
|
||||
"message": "Slet projekt"
|
||||
},
|
||||
|
||||
@@ -897,7 +897,7 @@
|
||||
"message": "Geschätzt {date}"
|
||||
},
|
||||
"dashboard.revenue.processing": {
|
||||
"message": "Verarbeitet"
|
||||
"message": "In verarbeitung"
|
||||
},
|
||||
"dashboard.revenue.processing.tooltip": {
|
||||
"message": "Einnahmen bleiben in bearbeitung bis zum ende des Monats, und werden 60 Tage danach verfügbar."
|
||||
@@ -987,19 +987,19 @@
|
||||
"message": "Konto nicht verknüpft"
|
||||
},
|
||||
"dashboard.withdraw.error.email-verification.text": {
|
||||
"message": "Du musst deine E-Mail-Adresse bestätigen, bevor du Geld abheben kannst."
|
||||
"message": "Du musst deine E-Mail-Adresse bestätigen, bevor du Geld auszahlen kannst."
|
||||
},
|
||||
"dashboard.withdraw.error.email-verification.title": {
|
||||
"message": "E-Mail-Bestätigung erforderlich"
|
||||
},
|
||||
"dashboard.withdraw.error.generic.text": {
|
||||
"message": "Wir konnten deine Auszahlungsanforderung nicht übermitteln. Bitte überprüfe deine Angaben oder wende dich an den Support.\n{error}"
|
||||
"message": "Wir konnten deine Auszahlungsanforderung nicht übermitteln. Bitte überprüfe deine Angaben oder kontaktiere den Support.\n{error}"
|
||||
},
|
||||
"dashboard.withdraw.error.generic.title": {
|
||||
"message": "Abheben nicht möglich"
|
||||
},
|
||||
"dashboard.withdraw.error.insufficient-balance.text": {
|
||||
"message": "Du hast nicht genug Guthaben, um eine Auszahlung vorzunehmen."
|
||||
"message": "Du hast nicht genug Guthaben, um diese Auszahlung vorzunehmen."
|
||||
},
|
||||
"dashboard.withdraw.error.insufficient-balance.title": {
|
||||
"message": "Unzureichender Kontostand"
|
||||
@@ -1029,7 +1029,7 @@
|
||||
"message": "Menge zu gering"
|
||||
},
|
||||
"dashboard.withdraw.error.paypal-country-mismatch.text": {
|
||||
"message": "Bitte nutze die richtige PayPal-Überweisungsoption für deine Region (USA oder International)."
|
||||
"message": "Bitte nutze die richtige PayPal-Überweisungsoption für deine Region (US oder International)."
|
||||
},
|
||||
"dashboard.withdraw.error.paypal-country-mismatch.title": {
|
||||
"message": "PayPal-Region stimmt nicht überein"
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Fehler beim generieren des Status von der API beim erstellen."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Falls du die offizielle Modrinth-Website aufrufen wolltest, besuch <link>https://modrinth.com</link>. Diese Vorschauversion wird von Modrinth-Mitarbeitern zu Testzwecken genutzt. Sie wurde unter Verwendung von <branch-link>{owner}/{branch}</branch-link> @ {commit} erstellt."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Dies ist eine Vorschauversion der Modrinth-Webseite."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "Die Staging-Umgebung ist komplett separat von der Produktions-Datenbank von Modrinth. Sie wird zu Test- und Debugging-Zwecken verwendet und könnte Entwicklungsversionen des Modrinth-Backends oder -Frontends, welche neuer als die Produkionsinstanz sind, ausführen."
|
||||
},
|
||||
|
||||
@@ -621,7 +621,7 @@
|
||||
"message": "Dein {formType} Steuerformular wurde erfolgreich eingereicht!"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.support-text": {
|
||||
"message": "Du kannst jetzt frei abheben. Wenn du Fragen hast oder deine Angaben aktualisieren musst, <support-link>wende dich an den Support</support-link>."
|
||||
"message": "Du kannst jetzt jederzeit auszahlen. Falls du Fragen hast oder deine Angaben aktualisieren möchtest, <support-link>kontaktiere den Support</support-link>."
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.title": {
|
||||
"message": "Du bist startklar! 🎉"
|
||||
@@ -897,7 +897,7 @@
|
||||
"message": "Geschätzt {date}"
|
||||
},
|
||||
"dashboard.revenue.processing": {
|
||||
"message": "Verarbeitet"
|
||||
"message": "In Verarbeitung"
|
||||
},
|
||||
"dashboard.revenue.processing.tooltip": {
|
||||
"message": "Einnahmen bleiben in bearbeitung bis zum ende des Monats, und werden 60 Tage danach verfügbar."
|
||||
@@ -912,7 +912,7 @@
|
||||
"message": "Ausgezahlt"
|
||||
},
|
||||
"dashboard.revenue.tos": {
|
||||
"message": "Durch das hochladen von Projekten auf Modrinth, und das abheben von Geld von deinem Konto, stimmst du den <terms-link>Bedingungen des Rewards-Programm</terms-link> zu. Erfahre mehr über das <info-link>Reward-Programm</info-link>."
|
||||
"message": "Durch das Hochladen von Projekten auf Modrinth und das Auszahlen von Geld von deinem Konto stimmst du unseren <terms-link>Bedingungen für das Prämienprogramm</terms-link> zu. Erfahre mehr über das <info-link>Prämienprogramm</info-link>."
|
||||
},
|
||||
"dashboard.revenue.transactions.btn.download-csv": {
|
||||
"message": "Als CSV herunterladen"
|
||||
@@ -936,10 +936,10 @@
|
||||
"message": "Hebe von deinem Verfügbaren Guthaben über einen Zahlungsmethode ab."
|
||||
},
|
||||
"dashboard.revenue.withdraw.card.title": {
|
||||
"message": "Abheben"
|
||||
"message": "Auszahlen"
|
||||
},
|
||||
"dashboard.revenue.withdraw.header": {
|
||||
"message": "Abheben"
|
||||
"message": "Auszahlen"
|
||||
},
|
||||
"dashboard.withdraw.completion.account": {
|
||||
"message": "Konto"
|
||||
@@ -987,7 +987,7 @@
|
||||
"message": "Konto nicht verknüpft"
|
||||
},
|
||||
"dashboard.withdraw.error.email-verification.text": {
|
||||
"message": "Du musst deine E-Mail-Adresse bestätigen, bevor du Geld abheben kannst."
|
||||
"message": "Du musst deine E-Mail-Adresse bestätigen, bevor du Geld auszahlen kannst."
|
||||
},
|
||||
"dashboard.withdraw.error.email-verification.title": {
|
||||
"message": "E-Mail-Bestätigung erforderlich"
|
||||
@@ -996,7 +996,7 @@
|
||||
"message": "Wir konnten deine Auszahlungsanforderung nicht übermitteln. Bitte überprüfe deine Angaben oder wende dich an den Support.\n{error}"
|
||||
},
|
||||
"dashboard.withdraw.error.generic.title": {
|
||||
"message": "Abheben nicht möglich"
|
||||
"message": "Auszahlung nicht möglich"
|
||||
},
|
||||
"dashboard.withdraw.error.insufficient-balance.text": {
|
||||
"message": "Du hast nicht genug Guthaben, um eine Auszahlung vorzunehmen."
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Fehler beim Generieren des Status aus der API beim Erstellen."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Falls du die offizielle Modrinth-Website aufrufen wolltest, besuch <link>https://modrinth.com</link>. Diese Vorschauversion wird von Modrinth-Mitarbeitern zu Testzwecken genutzt. Sie wurde unter Verwendung von <branch-link>{owner}/{branch}</branch-link> @ {commit} erstellt."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Dies ist eine Vorschauversion der Modrinth-Website."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "Die Staging-Umgebung ist vollständig von der Modrinth Produktionsdatenbank getrennt. Sie wird zu Test- und Debugging-Zwecken verwendet und kann Entwicklungsversionen des Modrinth Backends oder -Frontends ausführen, die neuer sind als die Produktionsinstanz."
|
||||
},
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
"app-marketing.download.download-rpm": {
|
||||
"message": "Download the RPM"
|
||||
},
|
||||
"app-marketing.download.flathub": {
|
||||
"message": "Get it on Flathub"
|
||||
},
|
||||
"app-marketing.download.linux": {
|
||||
"message": "Linux"
|
||||
},
|
||||
@@ -38,9 +41,6 @@
|
||||
"app-marketing.download.terms": {
|
||||
"message": "By downloading Modrinth App you agree to our <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
|
||||
},
|
||||
"app-marketing.download.third-party-packages": {
|
||||
"message": "Third-party packages"
|
||||
},
|
||||
"app-marketing.download.title": {
|
||||
"message": "Download Modrinth App (Beta)"
|
||||
},
|
||||
@@ -197,6 +197,15 @@
|
||||
"app-marketing.hero.more-download-options": {
|
||||
"message": "More Download Options"
|
||||
},
|
||||
"app-marketing.hide-other-packages": {
|
||||
"message": "Hide other packages"
|
||||
},
|
||||
"app-marketing.not-recommended": {
|
||||
"message": "We don't recommend you use these unless you know what you're doing."
|
||||
},
|
||||
"app-marketing.show-other-packages": {
|
||||
"message": "Show other packages"
|
||||
},
|
||||
"auth.authorize.action.authorize": {
|
||||
"message": "Authorize"
|
||||
},
|
||||
@@ -1337,6 +1346,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Error generating state from API when building."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "If you meant to access the official Modrinth website, visit <link>https://modrinth.com</link>. This preview deploy is used by Modrinth staff for testing purposes. It was built using <branch-link>{owner}/{branch}</branch-link> @ {commit}."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "This is a preview deploy of the Modrinth website."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "The staging environment is completely separate from the production Modrinth database. This is used for testing and debugging purposes, and may be running in-development versions of the Modrinth backend or frontend newer than the production instance."
|
||||
},
|
||||
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Error al generar el estado desde la API durante la compilación."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Si desea acceder al sitio web oficial de Modrinth, visite <link>https://modrinth.com</link>. El equipo de Modrinth utiliza esta versión preliminar para realizar pruebas. Se creó con <branch-link>{owner}/{branch}</branch-link> @ {commit}.."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Esta es una vista previa de la implementación del sitio web de Modrinth."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "El entorno de prueba está completamente separado de la base de datos de producción de Modrinth. Se utiliza con fines de prueba y depuración, y puede estar ejecutando versiones en desarrollo del backend o frontend de Modrinth más recientes que la instancia de producción."
|
||||
},
|
||||
|
||||
@@ -809,6 +809,9 @@
|
||||
"dashboard.creator-withdraw-modal.stage.tax-form": {
|
||||
"message": "Formulario fiscal"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.tremendous-details": {
|
||||
"message": "Detalles"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tax-form-required.body": {
|
||||
"message": "Para retirar el saldo total <b>{available}</b> disponible, rellene el siguiente formulario. Es necesario para la declaración de impuestos y solo hay que hacerlo una vez."
|
||||
},
|
||||
@@ -821,6 +824,9 @@
|
||||
"dashboard.creator-withdraw-modal.withdraw-button": {
|
||||
"message": "Retirada"
|
||||
},
|
||||
"dashboard.revenue.available-now": {
|
||||
"message": "Ya disponible"
|
||||
},
|
||||
"dashboard.revenue.tos": {
|
||||
"message": "Al subir proyectos a Modrinth y retirar dinero de tu cuenta, aceptas nuestros <terms-link>Términos del programa de recompensas</terms-link>. Más información sobre el <info-link>Programa de recompensas</info-link>."
|
||||
},
|
||||
@@ -836,6 +842,9 @@
|
||||
"dashboard.withdraw.completion.method": {
|
||||
"message": "Método"
|
||||
},
|
||||
"dashboard.withdraw.completion.wallet": {
|
||||
"message": "Cartera"
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-bank.text": {
|
||||
"message": ""
|
||||
},
|
||||
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "May error sa pagbuo ng estado galing sa API habang nagpapatayo."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Kung ibig mong maakses ang opisyal na website ng Modrinth, bisitahin ang <link>https://modrinth.com</link>. Ito ay isang pinakalat na pasilip na ginagamit lamang ng kawani ng Modrinth sa pagsusubok nito. Ito ay pinatayo gamit ang <branch-link>{owner}/{branch}</branch-link> @ {commit}."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Ito ay isang pinakalat na pasilip ng website ng Modrinth."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "Buong magkahiwalay ang staging environment sa production Modrinth database. Ginagamit ito sa pagsusuri at pag-debug, at maaaring pinatatakbo sa mga bersiyong in-development ng backend o frontend sa bagong production instance ng Modrinth."
|
||||
},
|
||||
@@ -1398,7 +1404,7 @@
|
||||
"message": "Legal"
|
||||
},
|
||||
"layout.footer.legal-disclaimer": {
|
||||
"message": "HINDI OPISYAL NA SERBISYO NG MINECRAFT. HINDI APRUBADO O KAAKIBAT NG MOJANG O MICROSOFT."
|
||||
"message": "HINDI OPISYAL NA SERBISYO NG MINECRAFT. HINDI APRUBADO O KAAKIBAT NG MOJANG O NG MICROSOFT."
|
||||
},
|
||||
"layout.footer.legal.copyright-policy": {
|
||||
"message": "Patakaran sa Copyright at DMCA"
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"message": "Suivre des projets"
|
||||
},
|
||||
"app-marketing.features.importing.description": {
|
||||
"message": "Importez tous vos profils depuis votre launcher précédent, et utilisez les dans l'application Modrinth en quelques secondes !"
|
||||
"message": "Importez tous vos profils préférés depuis votre launcher habituel et plongez dans Modrinth App en quelques secondes !"
|
||||
},
|
||||
"app-marketing.features.importing.gdlauncher-alt": {
|
||||
"message": "GDLauncher"
|
||||
@@ -108,7 +108,7 @@
|
||||
"message": "% CPU"
|
||||
},
|
||||
"app-marketing.features.performance.description": {
|
||||
"message": "L’application Modrinth est plus performante que la plupart des autres gestionnaires de mods, tout en n’utilisant que 150 Mo de RAM !"
|
||||
"message": "Modrinth App est plus performante que la plupart des autres gestionnaires de mods, tout en n’utilisant que 150 Mo de RAM !"
|
||||
},
|
||||
"app-marketing.features.performance.discord": {
|
||||
"message": "Discord"
|
||||
@@ -147,10 +147,10 @@
|
||||
"message": "Performant"
|
||||
},
|
||||
"app-marketing.features.play.description": {
|
||||
"message": "Utilisez l'application Modrinth pour télécharger et jouer avec vos mods et modpacks favoris."
|
||||
"message": "Utilisez Modrinth App pour télécharger et jouer avec vos mods et modpacks préférés."
|
||||
},
|
||||
"app-marketing.features.play.title": {
|
||||
"message": "Jouez avec vos mods favoris"
|
||||
"message": "Jouez avec vos mods préférés"
|
||||
},
|
||||
"app-marketing.features.sharing.description": {
|
||||
"message": "Créez, partagez et jouez sur des modpacks avec les milliers de mods et modpack présents ici sur Modrinth."
|
||||
@@ -162,10 +162,10 @@
|
||||
"message": "Partager"
|
||||
},
|
||||
"app-marketing.features.sharing.title": {
|
||||
"message": "Partager des modpacks"
|
||||
"message": "Partagez des modpacks"
|
||||
},
|
||||
"app-marketing.features.unlike-any-launcher": {
|
||||
"message": "Différent de tous les autres launchers"
|
||||
"message": "Un launcher comme"
|
||||
},
|
||||
"app-marketing.features.website.description": {
|
||||
"message": "L'application Modrinth est entièrement intégrée au site Web, vous pouvez donc accéder à tous vos projets préférés depuis l'application !"
|
||||
@@ -174,13 +174,13 @@
|
||||
"message": "Intégration au site Web"
|
||||
},
|
||||
"app-marketing.features.youve-used-before": {
|
||||
"message": "vous avez utilisé avant"
|
||||
"message": "vous n’en avez jamais vu"
|
||||
},
|
||||
"app-marketing.hero.app-screenshot-alt": {
|
||||
"message": "Capture d'écran de l'application Modrinth avec une instance Cobblemon ouverte sur la page \"Contenu\"."
|
||||
},
|
||||
"app-marketing.hero.description": {
|
||||
"message": "L'application Modrinth est un launcher unique et open source qui vous permet de jouer à vos mods favoris et de les maintenir à jour, le tout dans une seule petite application."
|
||||
"message": "Modrinth App est un launcher unique et open source qui vous permet de jouer à vos mods préférés et de les maintenir à jour, le tout dans une seule petite application."
|
||||
},
|
||||
"app-marketing.hero.download-button": {
|
||||
"message": "Télécharger l'application Modrinth"
|
||||
@@ -288,7 +288,7 @@
|
||||
"message": "Confirmer le mot de passe"
|
||||
},
|
||||
"auth.sign-up.email.label": {
|
||||
"message": "Adressse e-mail"
|
||||
"message": "Adresse e-mail"
|
||||
},
|
||||
"auth.sign-up.label.username": {
|
||||
"message": "Nom d'utilisateur"
|
||||
@@ -417,7 +417,7 @@
|
||||
"message": "Pas de projet dans la collection actuellement"
|
||||
},
|
||||
"collection.label.projects-count": {
|
||||
"message": "{count, plural, =0 {Aucun projets actuellement} one {<stat>{count}</stat> project} other {<stat>{count}</stat> {type}}}"
|
||||
"message": "{count, plural, =0 {Aucun projets actuellement} one {<stat>{count}</stat> projet} other {<stat>{count}</stat> {type}}}"
|
||||
},
|
||||
"collection.label.updated-at": {
|
||||
"message": "Mis à jour {ago}"
|
||||
@@ -459,7 +459,7 @@
|
||||
"message": "Entrez le nom de la collection..."
|
||||
},
|
||||
"create.collection.summary-description": {
|
||||
"message": "Une ou deux phrases qui décrivent vôtre collection."
|
||||
"message": "Une phrase ou deux qui décrivent votre collection."
|
||||
},
|
||||
"create.collection.summary-label": {
|
||||
"message": "Sommaire"
|
||||
@@ -1128,13 +1128,13 @@
|
||||
"message": "Utilisateur introuvable"
|
||||
},
|
||||
"frog": {
|
||||
"message": "Vous avez été grenouillé ! 🐸"
|
||||
"message": "Grenouille surprise ! 🐸"
|
||||
},
|
||||
"frog.altText": {
|
||||
"message": "Un tableau photoréaliste d'un labyrinthe amphibien"
|
||||
"message": "Un tableau photoréaliste du Labyrinthe des Grenouilles"
|
||||
},
|
||||
"frog.froggedPeople": {
|
||||
"message": "{count, plural, one {{count} autre personne a été grenouillée !} other {{count} autres personnes ont été grenouillées !}}"
|
||||
"message": "{count, plural, one {{count} autre personne a eu} other {{count} autres personnes ont eu}} une grenouille surprise !"
|
||||
},
|
||||
"frog.sinceOpened": {
|
||||
"message": "Cette page a été ouverte {ago}"
|
||||
@@ -1197,7 +1197,7 @@
|
||||
"message": "L’API open-source de Modrinth permet aux launchers d’ajouter des intégrations à Modrinth. Vous pouvez utiliser Modrinth depuis notre <link>application officielle</link> ou depuis les launchers les plus populaires, par exemple ATLauncher, MultiMC et Prism Launcher."
|
||||
},
|
||||
"landing.feature.launcher.heading": {
|
||||
"message": "Jouez avec vos launchers favoris"
|
||||
"message": "Jouez avec vos launchers préférés"
|
||||
},
|
||||
"landing.feature.search.description": {
|
||||
"message": "La recherche ultra-rapide de Modrinth et ses filtres puissants vous permettent de trouver ce que vous cherchez au fur et à mesure que vous tapez."
|
||||
@@ -1206,10 +1206,10 @@
|
||||
"message": "Trouvez ce que vous souhaitez, rapidement et facilement"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft": {
|
||||
"message": "L’endroit idéal pour les {content} Minecraft"
|
||||
"message": "Savourez Minecraft avec des {content}"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.data-packs": {
|
||||
"message": "data packs"
|
||||
"message": "packs de données"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.modpacks": {
|
||||
"message": "modpacks"
|
||||
@@ -1236,7 +1236,7 @@
|
||||
"message": "Une représentation simplifiée d'une fenêtre Minecraft, avec le logo de Mojang Studios en vert."
|
||||
},
|
||||
"landing.launcher.modrinth-app-label": {
|
||||
"message": "Application Modrinth"
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"landing.launcher.prism-launcher-label": {
|
||||
"message": "Prism Launcher"
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Erreur lors de la génération de l’état à partir de l’API pendant la construction."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Si vous souhaitez accéder au site officiel de Modrinth, visitez <link>https://modrinth.com</link>. Ce déploiement d'aperçu est utilisé par le personnel de Modrinth à des fins de test. Il a été construit en utilisant <branch-link>{owner}/{branch}</branch-link> @ {commit}."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Ceci est un déploiement de prévisualisation du site Modrinth."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "L'environnement d'essai fonctionne sur une copie de la base de données en production de Modrinth. Il est utilisé à des fins de test et de débogage, et peut exécuter des versions en cours de développement du backend ou du frontend de Modrinth plus récentes que l'instance en production."
|
||||
},
|
||||
@@ -1425,13 +1431,13 @@
|
||||
"message": "Produits"
|
||||
},
|
||||
"layout.footer.products.app": {
|
||||
"message": "Application Modrinth"
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"layout.footer.products.plus": {
|
||||
"message": "Modrinth+"
|
||||
},
|
||||
"layout.footer.products.servers": {
|
||||
"message": "Hébergement Modrinth"
|
||||
"message": "Modrinth Hosting"
|
||||
},
|
||||
"layout.footer.resources": {
|
||||
"message": "Ressources"
|
||||
@@ -1494,7 +1500,7 @@
|
||||
"message": "Fonctionnalités expérimentales"
|
||||
},
|
||||
"layout.nav.get-modrinth-app": {
|
||||
"message": "Obtenir l’application Modrinth"
|
||||
"message": "Obtenir Modrinth App"
|
||||
},
|
||||
"layout.nav.home": {
|
||||
"message": "Accueil"
|
||||
@@ -1503,7 +1509,7 @@
|
||||
"message": "Héberger un serveur"
|
||||
},
|
||||
"layout.nav.modrinth-app": {
|
||||
"message": "Application Modrinth"
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"layout.nav.modrinth-home-page": {
|
||||
"message": "Page d'accueil de Modrinth"
|
||||
@@ -1622,6 +1628,9 @@
|
||||
"muralpay.document-type.resident-id": {
|
||||
"message": "Carte d'identité résidente"
|
||||
},
|
||||
"muralpay.document-type.ruc": {
|
||||
"message": "RUC"
|
||||
},
|
||||
"muralpay.document-type.tax-id": {
|
||||
"message": "Numéro d'identification fiscale"
|
||||
},
|
||||
@@ -1646,9 +1655,18 @@
|
||||
"muralpay.field.branch-code": {
|
||||
"message": "Identifiant de branche"
|
||||
},
|
||||
"muralpay.field.clabe": {
|
||||
"message": "CLABE"
|
||||
},
|
||||
"muralpay.field.country": {
|
||||
"message": "Pays"
|
||||
},
|
||||
"muralpay.field.cpf-cnpj": {
|
||||
"message": "CPF/CNPJ"
|
||||
},
|
||||
"muralpay.field.cuit-cuil": {
|
||||
"message": "CUIT/CUIL"
|
||||
},
|
||||
"muralpay.field.document-type": {
|
||||
"message": "Type de document"
|
||||
},
|
||||
@@ -1658,6 +1676,15 @@
|
||||
"muralpay.field.phone-number": {
|
||||
"message": "Numéro de téléphone"
|
||||
},
|
||||
"muralpay.field.pix-email": {
|
||||
"message": "Adresse e-mail PIX"
|
||||
},
|
||||
"muralpay.field.pix-key-type": {
|
||||
"message": "Type de clef PIX"
|
||||
},
|
||||
"muralpay.field.pix-phone": {
|
||||
"message": "Téléphone PIX"
|
||||
},
|
||||
"muralpay.field.routing-number": {
|
||||
"message": "Numéro de routage"
|
||||
},
|
||||
@@ -1668,11 +1695,14 @@
|
||||
"message": "Adresse du portefeuille"
|
||||
},
|
||||
"muralpay.help.cbu-cvu": {
|
||||
"message": ""
|
||||
"message": "Code Bancaire Uniforme ou Code Virtuel Uniforme"
|
||||
},
|
||||
"muralpay.help.cci": {
|
||||
"message": "Code interbancaire"
|
||||
},
|
||||
"muralpay.help.clabe": {
|
||||
"message": "Clave Bancaria Estandarizada (numéro de compte bancaire mexicain)"
|
||||
},
|
||||
"muralpay.help.cpf-cnpj": {
|
||||
"message": "Numéro d'identification fiscale brésilien"
|
||||
},
|
||||
@@ -1700,15 +1730,45 @@
|
||||
"muralpay.placeholder.account-number": {
|
||||
"message": "Entrée le numéro de compte"
|
||||
},
|
||||
"muralpay.placeholder.cbu-cvu": {
|
||||
"message": "Saisissez votre CBU ou CVU"
|
||||
},
|
||||
"muralpay.placeholder.cbu-cvu-type": {
|
||||
"message": "CBU ou CVU"
|
||||
},
|
||||
"muralpay.placeholder.cci": {
|
||||
"message": "Saisissez le code CCI à 20 chiffres"
|
||||
},
|
||||
"muralpay.placeholder.cuit-cuil": {
|
||||
"message": "Saisissez votre CUIT ou CUIL"
|
||||
},
|
||||
"muralpay.placeholder.enter-account-number": {
|
||||
"message": "Saisissez le numéro de compte"
|
||||
},
|
||||
"muralpay.placeholder.enter-branch-code": {
|
||||
"message": "Saisissez le code de l'agence"
|
||||
},
|
||||
"muralpay.placeholder.enter-clabe": {
|
||||
"message": "Entrez la CLABE à 18 chiffres"
|
||||
},
|
||||
"muralpay.placeholder.enter-cpf-cnpj": {
|
||||
"message": "Saisissez votre CPF ou CNPJ"
|
||||
},
|
||||
"muralpay.placeholder.enter-iban": {
|
||||
"message": "Saisissez l'IBAN"
|
||||
},
|
||||
"muralpay.placeholder.enter-pix-email": {
|
||||
"message": "Saisissez votre adresse e-mail PIX"
|
||||
},
|
||||
"muralpay.placeholder.enter-routing-number": {
|
||||
"message": "Saisissez le numéro de routage à 9 chiffres"
|
||||
},
|
||||
"muralpay.placeholder.enter-swift-bic": {
|
||||
"message": "Saisissez le code SWIFT/BIC"
|
||||
},
|
||||
"muralpay.placeholder.iban-crc": {
|
||||
"message": "Saisissez votre IBAN costaricien"
|
||||
},
|
||||
"muralpay.placeholder.phone-cop": {
|
||||
"message": "+57..."
|
||||
},
|
||||
@@ -1718,15 +1778,51 @@
|
||||
"muralpay.placeholder.wallet-address-eth": {
|
||||
"message": "0x..."
|
||||
},
|
||||
"muralpay.rail.fiat-ars.name": {
|
||||
"message": "Virement bancaire (ARS)"
|
||||
},
|
||||
"muralpay.rail.fiat-brl.name": {
|
||||
"message": "Virement PIX (BRL)"
|
||||
},
|
||||
"muralpay.rail.fiat-clp.name": {
|
||||
"message": "Virement bancaire (CLP)"
|
||||
},
|
||||
"muralpay.rail.fiat-cop.name": {
|
||||
"message": "Virement bancaire (COP)"
|
||||
},
|
||||
"muralpay.rail.fiat-crc.name": {
|
||||
"message": "Virement bancaire (CRC)"
|
||||
},
|
||||
"muralpay.rail.fiat-eur.name": {
|
||||
"message": "Transfert bancaire (EUR)"
|
||||
},
|
||||
"muralpay.rail.fiat-mxn.name": {
|
||||
"message": "Transfert bancaire (MXN)"
|
||||
},
|
||||
"muralpay.rail.fiat-pen.name": {
|
||||
"message": "Virement bancaire (PEN)"
|
||||
},
|
||||
"muralpay.rail.fiat-usd-peru.name": {
|
||||
"message": "Virement bancaire (USD - Pérou)"
|
||||
},
|
||||
"muralpay.rail.fiat-usd.name": {
|
||||
"message": "Virement bancaire (USD)"
|
||||
},
|
||||
"muralpay.rail.fiat-zar.name": {
|
||||
"message": "Virement bancaire (ZAR)"
|
||||
},
|
||||
"muralpay.rail.usdc-base.name": {
|
||||
"message": "USDC (Base)"
|
||||
},
|
||||
"muralpay.rail.usdc-celo.name": {
|
||||
"message": "USDC (Celo)"
|
||||
},
|
||||
"muralpay.rail.usdc-ethereum.name": {
|
||||
"message": "USDC (Ethereum)"
|
||||
},
|
||||
"muralpay.rail.usdc-polygon.name": {
|
||||
"message": "Crypto (USDC)"
|
||||
},
|
||||
"muralpay.warning.wallet-address": {
|
||||
"message": "Vérifiez votre adresse. Les fonds envoyés à une adresse incorrecte ne pourront pas être récupérés."
|
||||
},
|
||||
@@ -1754,12 +1850,21 @@
|
||||
"profile.button.set-affiliate": {
|
||||
"message": "Se mettre comme affilié"
|
||||
},
|
||||
"profile.details.label.auth-providers": {
|
||||
"message": "Fournisseurs d’authentification"
|
||||
},
|
||||
"profile.details.label.email": {
|
||||
"message": "Adresse e-mail"
|
||||
},
|
||||
"profile.details.label.email-verified": {
|
||||
"message": "Adresse e-mail vérifiée"
|
||||
},
|
||||
"profile.details.label.has-password": {
|
||||
"message": "Dispose d'un mot de passe"
|
||||
},
|
||||
"profile.details.label.has-totp": {
|
||||
"message": "Dispose d'un TOTP"
|
||||
},
|
||||
"profile.details.label.payment-methods": {
|
||||
"message": "Moyens de paiements"
|
||||
},
|
||||
@@ -1787,6 +1892,9 @@
|
||||
"profile.label.downloads": {
|
||||
"message": "{count} {count, plural, one {téléchargement} other {téléchargements}}"
|
||||
},
|
||||
"profile.label.joined": {
|
||||
"message": "Rejoint"
|
||||
},
|
||||
"profile.label.no": {
|
||||
"message": "Non"
|
||||
},
|
||||
@@ -1947,7 +2055,7 @@
|
||||
"message": "Sous licence {license}"
|
||||
},
|
||||
"project.about.details.published": {
|
||||
"message": "Publié le {date}"
|
||||
"message": "Publié {date}"
|
||||
},
|
||||
"project.about.details.submitted": {
|
||||
"message": "Soumis le {date}"
|
||||
@@ -2004,10 +2112,10 @@
|
||||
"message": "{title} ne supporte pas {gameVersion} pour {platform}"
|
||||
},
|
||||
"project.download.install-with-app": {
|
||||
"message": "Installer avec l'application Modrinth"
|
||||
"message": "Installer avec Modrinth App"
|
||||
},
|
||||
"project.download.no-app": {
|
||||
"message": "Vous n'avez pas l'application Modrinth ?"
|
||||
"message": "Vous n'avez pas Modrinth App ?"
|
||||
},
|
||||
"project.download.no-versions-available": {
|
||||
"message": "Aucune version disponible pour {gameVersion} et {platform}."
|
||||
@@ -2051,6 +2159,9 @@
|
||||
"project.environment.migration.learn-more": {
|
||||
"message": "En apprendre davantage au sujet de cette modification"
|
||||
},
|
||||
"project.environment.migration.message": {
|
||||
"message": "Nous venons de refaire le système d’environnements sur Modrinth et de nouvelles options sont désormais disponibles. Veuillez vérifier que les métadonnées sont correctes."
|
||||
},
|
||||
"project.environment.migration.review-button": {
|
||||
"message": "Vérifier les paramètres d'environnement"
|
||||
},
|
||||
@@ -2085,7 +2196,7 @@
|
||||
"message": "Journal des modifications"
|
||||
},
|
||||
"project.notification.icon-updated.message": {
|
||||
"message": "."
|
||||
"message": "L'icône de votre projet a été mis à jour."
|
||||
},
|
||||
"project.notification.icon-updated.title": {
|
||||
"message": "Icône du projet mis à jour"
|
||||
@@ -2148,10 +2259,10 @@
|
||||
"message": "Visiter le tableau de contrôle des projets"
|
||||
},
|
||||
"project.stats.downloads-label": {
|
||||
"message": "Téléchargement{count, plural, one {} other {s}}"
|
||||
"message": "téléchargement{count, plural, one {} other {s}}"
|
||||
},
|
||||
"project.stats.followers-label": {
|
||||
"message": "Abonné{count, plural, one {} other {s}}"
|
||||
"message": "abonné{count, plural, one {} other {s}}"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} a été archivé. {title} ne recevra plus de mises à jour jusqu'à ce que l'auteur du projet décide de désarchiver le projet."
|
||||
@@ -2511,7 +2622,7 @@
|
||||
"message": "Niveau"
|
||||
},
|
||||
"servers.notice.undismissable": {
|
||||
"message": "Non rejetable"
|
||||
"message": "Non rejetable"
|
||||
},
|
||||
"servers.notices.create-notice": {
|
||||
"message": "Créer un avis"
|
||||
@@ -2682,10 +2793,10 @@
|
||||
"message": "Ouvre les liens externes dans un nouvel onglet"
|
||||
},
|
||||
"settings.display.sidebar.hide-app-promos.description": {
|
||||
"message": "Cache les boutons \"Obtenir l'Application Modrinth\" de la navigation principale. La page de l'application Modrinth peut toujours être trouvée sur la page d'accueil ou dans le pied de page."
|
||||
"message": "Cache les boutons \"Obtenir Modrinth App\" de la navigation principale. La page de Modrinth App peut toujours être trouvée sur la page d'accueil ou dans le pied de page."
|
||||
},
|
||||
"settings.display.sidebar.hide-app-promos.title": {
|
||||
"message": "Cache les publicités de l'application Modrinth"
|
||||
"message": "Cache les publicités de Modrinth App"
|
||||
},
|
||||
"settings.display.sidebar.left-aligned-content-sidebar.title": {
|
||||
"message": "Barre latérale alignée à gauche sur les pages de contenus"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"en-US": "Anglais (États-Unis)",
|
||||
"en-x-lolcat": "LOLCAT",
|
||||
"en-x-pirate": "Anglais (Pirate)",
|
||||
"en-x-updown": "Anglais (à l'envers)",
|
||||
"en-x-updown": "Anglais (À l'envers)",
|
||||
"en-x-uwu": "Anglais (UwU)",
|
||||
"eo": "Espéranto",
|
||||
"es": "Espagnol",
|
||||
|
||||
@@ -1026,7 +1026,7 @@
|
||||
"message": "Találd meg gyorsan és egyszerűen, amit keresel"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft": {
|
||||
"message": "Hely a Minecraft {content}"
|
||||
"message": "Egy hely a Minecraft {content}"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.data-packs": {
|
||||
"message": "adatcsomagoknak"
|
||||
@@ -1250,6 +1250,9 @@
|
||||
"layout.nav.home": {
|
||||
"message": "Kezdőlap"
|
||||
},
|
||||
"layout.nav.host-a-server": {
|
||||
"message": "Hozz létre egy szervert"
|
||||
},
|
||||
"layout.nav.modrinth-app": {
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
|
||||
@@ -1056,7 +1056,7 @@
|
||||
"message": "Periksa apakah Modrinth sedang mengalami gangguan di <status-link>halaman Status</status-link>."
|
||||
},
|
||||
"error.generic.default.list_item.2": {
|
||||
"message": "Bila hal ini terus terjadi, Anda mungkin ingin memberi tahu Tim Modrinth dengan bergabung ke <discord-link>peladen Discord</discord-link> kami."
|
||||
"message": "Bila hal ini terus terjadi, Anda mungkin ingin memberi tahu Tim Modrinth dengan bergabung ke <discord-link>server Discord</discord-link> kami."
|
||||
},
|
||||
"error.generic.default.list_title": {
|
||||
"message": "Silakan coba lagi dalam beberapa menit."
|
||||
@@ -1146,7 +1146,7 @@
|
||||
"message": "Data dan Statistik"
|
||||
},
|
||||
"landing.creator.feature.discovery.description": {
|
||||
"message": "Kesempatan untuk proyek Anda agar dapat ditemukan oleh ribuan pengguna melalui pencarian, halaman beranda kami, peladen Discord, dan cara-cara lainnya akan hadir di masa mendatang!"
|
||||
"message": "Kesempatan untuk proyek Anda agar dapat ditemukan oleh ribuan pengguna melalui pencarian, halaman beranda kami, server Discord, dan cara-cara lainnya akan hadir di masa mendatang!"
|
||||
},
|
||||
"landing.creator.feature.discovery.title": {
|
||||
"message": "Penemuan"
|
||||
@@ -1209,7 +1209,7 @@
|
||||
"message": "paket sumber"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.servers": {
|
||||
"message": "peladen"
|
||||
"message": "server"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.shaders": {
|
||||
"message": "pencorak"
|
||||
@@ -1284,7 +1284,7 @@
|
||||
"message": "Kelola tautan afiliasi"
|
||||
},
|
||||
"layout.action.manage-server-notices": {
|
||||
"message": "Kelola pemberitahuan peladen"
|
||||
"message": "Kelola pemberitahuan server"
|
||||
},
|
||||
"layout.action.new-collection": {
|
||||
"message": "Koleksi baru"
|
||||
@@ -1485,7 +1485,7 @@
|
||||
"message": "Beranda"
|
||||
},
|
||||
"layout.nav.host-a-server": {
|
||||
"message": "Jalankan peladen"
|
||||
"message": "Jalankan server"
|
||||
},
|
||||
"layout.nav.modrinth-app": {
|
||||
"message": "Modrinth App"
|
||||
@@ -1494,7 +1494,7 @@
|
||||
"message": "Beranda Modrinth"
|
||||
},
|
||||
"layout.nav.my-servers": {
|
||||
"message": "Peladen saya"
|
||||
"message": "Server saya"
|
||||
},
|
||||
"layout.nav.organizations": {
|
||||
"message": "Organisasi"
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"message": "Memorizza i contenuti che ami e ricevi aggiornamenti con un clic."
|
||||
},
|
||||
"app-marketing.features.follow.title": {
|
||||
"message": "Segui progetti"
|
||||
"message": "Segui i progetti che ami"
|
||||
},
|
||||
"app-marketing.features.importing.description": {
|
||||
"message": "Importa i tuoi profili preferiti dal launcher che usavi prima e parti con Modrinth App in pochi secondi!"
|
||||
@@ -63,7 +63,7 @@
|
||||
"message": "MultiMC"
|
||||
},
|
||||
"app-marketing.features.importing.title": {
|
||||
"message": "Importazione profili"
|
||||
"message": "Importa profili"
|
||||
},
|
||||
"app-marketing.features.mod-management.actions": {
|
||||
"message": "Azioni"
|
||||
@@ -84,7 +84,7 @@
|
||||
"message": "Cerca mod"
|
||||
},
|
||||
"app-marketing.features.mod-management.title": {
|
||||
"message": "Gestione mod"
|
||||
"message": "Gestisci mod"
|
||||
},
|
||||
"app-marketing.features.mod-management.version": {
|
||||
"message": "Versione"
|
||||
@@ -99,7 +99,7 @@
|
||||
"message": "Modrinth App è del tutto open source. Leggi il codice sorgente sul nostro <github-link>GitHub</github-link>!"
|
||||
},
|
||||
"app-marketing.features.open-source.title": {
|
||||
"message": "Open Source"
|
||||
"message": "Open source"
|
||||
},
|
||||
"app-marketing.features.performance.activity-monitor": {
|
||||
"message": "Gestore attività"
|
||||
@@ -153,7 +153,7 @@
|
||||
"message": "Gioca con le tue mod preferite"
|
||||
},
|
||||
"app-marketing.features.sharing.description": {
|
||||
"message": "Costruisci, condividi e gioca con le migliaia di mod e pacchetti presenti qui su Modrinth."
|
||||
"message": "Trova e scegli tra le migliaia di mod e pacchetti presenti qui su Modrinth."
|
||||
},
|
||||
"app-marketing.features.sharing.modpack": {
|
||||
"message": "Pacchetto di mod"
|
||||
@@ -162,7 +162,7 @@
|
||||
"message": "Condividi"
|
||||
},
|
||||
"app-marketing.features.sharing.title": {
|
||||
"message": "Condividi pacchetto di mod"
|
||||
"message": "Trova, crea e condividi"
|
||||
},
|
||||
"app-marketing.features.unlike-any-launcher": {
|
||||
"message": "Distinta dagli altri launcher"
|
||||
@@ -171,7 +171,7 @@
|
||||
"message": "Modrinth App è integrata completamente con il sito, così puoi accedere a tutti i tuoi progetti preferiti dall'app!"
|
||||
},
|
||||
"app-marketing.features.website.title": {
|
||||
"message": "Integrazione con il sito"
|
||||
"message": "Integrata col sito"
|
||||
},
|
||||
"app-marketing.features.youve-used-before": {
|
||||
"message": "che tu abbia mai usato prima"
|
||||
@@ -1191,7 +1191,7 @@
|
||||
"message": "Ottieni notifiche per essere al corrente di ogni novità dei tuoi progetti preferiti."
|
||||
},
|
||||
"landing.feature.follow.heading": {
|
||||
"message": "Segui progetti che ami"
|
||||
"message": "Segui i progetti che ami"
|
||||
},
|
||||
"landing.feature.launcher.description": {
|
||||
"message": "L'API open source di Modrinth permette ai launcher d'integrarsi profondamente con Modrinth. Puoi usare Modrinth tramite la <link>nostra app</link>, e anche su altri launcher popolari come ATLauncher, MultiMC, e Prism Launcher."
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Errore nella generazione dello stato da API durante il build."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Se volevi accedere al sito ufficiale, visita <link>https://modrinth.com</link>. Questa versione serve allo staff di Modrinth per condurre test; basata su <branch-link>{owner}/{branch}</branch-link> @ {commit}."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Questa versione del sito di Modrinth è un'anteprima."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "L'ambiente di staging è completamente separato dal database di produzione di Modrinth. Questo ambiente è usato per testing e debugging e potrebbe eseguire versioni ancora in sviluppo del backend o del frontend di Modrinth."
|
||||
},
|
||||
@@ -1383,7 +1389,7 @@
|
||||
"message": "Lavora con noi"
|
||||
},
|
||||
"layout.footer.about.changelog": {
|
||||
"message": "Changelog"
|
||||
"message": "Note di versione"
|
||||
},
|
||||
"layout.footer.about.news": {
|
||||
"message": "Novità"
|
||||
@@ -2187,7 +2193,7 @@
|
||||
"message": "Moderazione"
|
||||
},
|
||||
"project.navigation.changelog": {
|
||||
"message": "Changelog"
|
||||
"message": "Novità"
|
||||
},
|
||||
"project.notification.icon-updated.message": {
|
||||
"message": "L'icona del tuo progetto è stata aggiornata."
|
||||
@@ -2748,7 +2754,7 @@
|
||||
"message": "Pagina dei pacchetti di dati"
|
||||
},
|
||||
"settings.display.project-list-layouts.description": {
|
||||
"message": "Seleziona il tuo layout preferito su questo dispositivo per ogni pagina che mostri elenchi di progetti."
|
||||
"message": "Scegli come disporre gli elenchi su questo dispositivo."
|
||||
},
|
||||
"settings.display.project-list-layouts.mod": {
|
||||
"message": "Pagina delle mod"
|
||||
@@ -2766,7 +2772,7 @@
|
||||
"message": "Pagina delle shader"
|
||||
},
|
||||
"settings.display.project-list-layouts.title": {
|
||||
"message": "Layout della lista progetti"
|
||||
"message": "Disposizione delle liste"
|
||||
},
|
||||
"settings.display.project-list-layouts.user": {
|
||||
"message": "Pagine dei profili utente"
|
||||
@@ -2805,7 +2811,7 @@
|
||||
"message": "Sidebar dei filtri a destra nelle pagine di ricerca"
|
||||
},
|
||||
"settings.display.theme.description": {
|
||||
"message": "Seleziona il tuo tema preferito per Modrinth su questo dispositivo."
|
||||
"message": "Scegli il tema che preferisci per Modrinth su questo dispositivo."
|
||||
},
|
||||
"settings.display.theme.title": {
|
||||
"message": "Tema"
|
||||
|
||||
@@ -176,6 +176,9 @@
|
||||
"app-marketing.features.youve-used-before": {
|
||||
"message": "以前に使用したことある"
|
||||
},
|
||||
"app-marketing.hero.app-screenshot-alt": {
|
||||
"message": ""
|
||||
},
|
||||
"app-marketing.hero.description": {
|
||||
"message": "Modrinth Appは、お気に入りのModで遊んだり、アップデートを簡単に管理できる、独自のオープンソースランチャーです。"
|
||||
},
|
||||
@@ -188,6 +191,9 @@
|
||||
"app-marketing.hero.download-modrinth-app-for-os": {
|
||||
"message": "{os}向けのModrinth Appをダウンロード"
|
||||
},
|
||||
"app-marketing.hero.minecraft-screenshot-alt": {
|
||||
"message": "。"
|
||||
},
|
||||
"app-marketing.hero.more-download-options": {
|
||||
"message": "詳細なダウンロード設定"
|
||||
},
|
||||
@@ -797,6 +803,15 @@
|
||||
"dashboard.creator-withdraw-modal.stage.completion": {
|
||||
"message": "完了"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.method-selection": {
|
||||
"message": ""
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.muralpay-details": {
|
||||
"message": ""
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.muralpay-kyc": {
|
||||
"message": ""
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.tax-form": {
|
||||
"message": "納税申告書"
|
||||
},
|
||||
@@ -944,6 +959,9 @@
|
||||
"dashboard.withdraw.completion.recipient": {
|
||||
"message": "受取人"
|
||||
},
|
||||
"dashboard.withdraw.completion.title": {
|
||||
"message": "出金完了"
|
||||
},
|
||||
"dashboard.withdraw.completion.transactions-button": {
|
||||
"message": "取引"
|
||||
},
|
||||
@@ -1298,9 +1316,15 @@
|
||||
"layout.banner.subscription-payment-failed.title": {
|
||||
"message": "お支払い手続きが必要です。"
|
||||
},
|
||||
"layout.banner.tax.action": {
|
||||
"message": "納税申告書を記入"
|
||||
},
|
||||
"layout.banner.tax.description": {
|
||||
"message": "今年、既にModrinthから 600 ドル以上を引き出しています。税務上の規定を遵守するため、税務フォームの提出が必要です。このフォームが提出されるまで、引き出しは一時停止されます。"
|
||||
},
|
||||
"layout.banner.tax.title": {
|
||||
"message": "納税申告書が必要"
|
||||
},
|
||||
"layout.banner.tin-mismatch.action": {
|
||||
"message": "サポートに連絡"
|
||||
},
|
||||
@@ -1421,6 +1445,9 @@
|
||||
"layout.nav.analytics": {
|
||||
"message": "統計"
|
||||
},
|
||||
"layout.nav.discover": {
|
||||
"message": "探索"
|
||||
},
|
||||
"layout.nav.get-modrinth-app": {
|
||||
"message": "Modrinthアプリを入手"
|
||||
},
|
||||
@@ -1433,6 +1460,9 @@
|
||||
"layout.nav.modrinth-app": {
|
||||
"message": "Modrinthアプリ"
|
||||
},
|
||||
"layout.nav.my-servers": {
|
||||
"message": "自分のサーバー"
|
||||
},
|
||||
"layout.nav.organizations": {
|
||||
"message": "組織"
|
||||
},
|
||||
@@ -1490,27 +1520,108 @@
|
||||
"muralpay.country.ee": {
|
||||
"message": "エストニア"
|
||||
},
|
||||
"muralpay.country.es": {
|
||||
"message": "スペイン"
|
||||
},
|
||||
"muralpay.country.fi": {
|
||||
"message": "フィンランド"
|
||||
},
|
||||
"muralpay.country.fr": {
|
||||
"message": "フランス"
|
||||
},
|
||||
"muralpay.country.gr": {
|
||||
"message": "ギリシャ"
|
||||
},
|
||||
"muralpay.country.ie": {
|
||||
"message": "アイルランド"
|
||||
},
|
||||
"muralpay.country.it": {
|
||||
"message": "イタリア"
|
||||
},
|
||||
"muralpay.country.lt": {
|
||||
"message": "リトアニア"
|
||||
},
|
||||
"muralpay.country.lu": {
|
||||
"message": "ルクセンブルク"
|
||||
},
|
||||
"muralpay.country.lv": {
|
||||
"message": "ラトビア"
|
||||
},
|
||||
"muralpay.country.mt": {
|
||||
"message": "マルタ"
|
||||
},
|
||||
"muralpay.country.nl": {
|
||||
"message": "オランダ"
|
||||
},
|
||||
"muralpay.country.pt": {
|
||||
"message": "ポルトガル"
|
||||
},
|
||||
"muralpay.country.sk": {
|
||||
"message": "スロベキア"
|
||||
},
|
||||
"muralpay.document-type.national-id": {
|
||||
"message": "国民ID"
|
||||
},
|
||||
"muralpay.document-type.passport": {
|
||||
"message": "パスポート"
|
||||
},
|
||||
"muralpay.document-type.ruc": {
|
||||
"message": "RUC"
|
||||
},
|
||||
"muralpay.document-type.tax-id": {
|
||||
"message": "納税者番号"
|
||||
},
|
||||
"muralpay.field.account-number": {
|
||||
"message": "口座番号"
|
||||
},
|
||||
"muralpay.field.account-number-cbu-cvu": {
|
||||
"message": "口座番号(CBU/CYU)"
|
||||
},
|
||||
"muralpay.field.account-number-cci": {
|
||||
"message": "口座番号(CCI)"
|
||||
},
|
||||
"muralpay.field.account-number-type": {
|
||||
"message": "口座番号種別"
|
||||
},
|
||||
"muralpay.field.account-type": {
|
||||
"message": "口座種別"
|
||||
},
|
||||
"muralpay.field.bank-account-number": {
|
||||
"message": "口座番号"
|
||||
},
|
||||
"muralpay.field.country": {
|
||||
"message": "国"
|
||||
},
|
||||
"muralpay.field.phone-number": {
|
||||
"message": "電話番号"
|
||||
},
|
||||
"muralpay.field.wallet-address": {
|
||||
"message": "ウォレットアドレス"
|
||||
},
|
||||
"muralpay.pix-type.bank-account": {
|
||||
"message": "銀行口座"
|
||||
},
|
||||
"muralpay.pix-type.document": {
|
||||
"message": "CPF/CPNJ"
|
||||
},
|
||||
"muralpay.pix-type.email": {
|
||||
"message": "メールアドレス"
|
||||
},
|
||||
"muralpay.pix-type.phone": {
|
||||
"message": "電話番号"
|
||||
},
|
||||
"muralpay.placeholder.account-number": {
|
||||
"message": "口座番号を入力"
|
||||
},
|
||||
"muralpay.placeholder.cbu-cvu": {
|
||||
"message": "CBUまたはCVUを入力"
|
||||
},
|
||||
"muralpay.placeholder.enter-account-number": {
|
||||
"message": "口座番号を入力"
|
||||
},
|
||||
"muralpay.placeholder.enter-iban": {
|
||||
"message": "IBANを入力"
|
||||
},
|
||||
"muralpay.placeholder.phone-cop": {
|
||||
"message": "+57..."
|
||||
},
|
||||
@@ -1520,6 +1631,12 @@
|
||||
"muralpay.placeholder.wallet-address-eth": {
|
||||
"message": "0x..."
|
||||
},
|
||||
"profile.bio.fallback.creator": {
|
||||
"message": "Modrinthの作成者。"
|
||||
},
|
||||
"profile.bio.fallback.user": {
|
||||
"message": "Modrinthのユーザー。"
|
||||
},
|
||||
"profile.button.info": {
|
||||
"message": "ユーザー情報を表示"
|
||||
},
|
||||
@@ -1538,12 +1655,21 @@
|
||||
"profile.label.badges": {
|
||||
"message": "バッジ"
|
||||
},
|
||||
"profile.label.collection": {
|
||||
"message": "コレクション"
|
||||
},
|
||||
"profile.label.details": {
|
||||
"message": "詳細"
|
||||
},
|
||||
"profile.label.downloads": {
|
||||
"message": "{count} 件の{count, plural, one {ダウンロード} other {ダウンロード}}"
|
||||
},
|
||||
"profile.label.joined": {
|
||||
"message": "参加しました"
|
||||
},
|
||||
"profile.label.no": {
|
||||
"message": "いいえ"
|
||||
},
|
||||
"profile.label.no-collections": {
|
||||
"message": "このユーザーのコレクションはありません!"
|
||||
},
|
||||
@@ -1589,6 +1715,9 @@
|
||||
"project-member-header.error-decline": {
|
||||
"message": "チームへの招待を辞退できませんでした"
|
||||
},
|
||||
"project-member-header.invitation-title": {
|
||||
"message": "プロジェクトへの参加招待"
|
||||
},
|
||||
"project-member-header.success-join": {
|
||||
"message": "あなたはプロジェクトに参加しました"
|
||||
},
|
||||
@@ -1601,6 +1730,9 @@
|
||||
"project-moderation-nags.submit-for-review-button": {
|
||||
"message": "レビューを提出する"
|
||||
},
|
||||
"project-moderation-nags.warning": {
|
||||
"message": "警告"
|
||||
},
|
||||
"project-type.collection.plural": {
|
||||
"message": "コレクション"
|
||||
},
|
||||
@@ -1631,6 +1763,18 @@
|
||||
"project-type.shader.plural": {
|
||||
"message": "シェーダー"
|
||||
},
|
||||
"project.about.details.created": {
|
||||
"message": "{date}に作成"
|
||||
},
|
||||
"project.actions.create-server": {
|
||||
"message": "サーバーを作成"
|
||||
},
|
||||
"project.actions.create-server-tooltip": {
|
||||
"message": "サーバーを作成"
|
||||
},
|
||||
"project.actions.dont-show-again": {
|
||||
"message": "再度表示しない"
|
||||
},
|
||||
"project.actions.servers-promo.description": {
|
||||
"message": "Modrinth Hostingは手間を掛けず、簡単に友達と遊べます!"
|
||||
},
|
||||
@@ -1638,7 +1782,10 @@
|
||||
"message": "サーバーを作成"
|
||||
},
|
||||
"project.collections.create-new": {
|
||||
"message": "コレクションを作成"
|
||||
"message": "新規コレクションを作成"
|
||||
},
|
||||
"project.collections.none-found": {
|
||||
"message": "コレクションを見つかりませんでした。"
|
||||
},
|
||||
"project.download.game-version": {
|
||||
"message": "ゲームバージョン: {version}"
|
||||
@@ -1721,9 +1868,66 @@
|
||||
"project.versions.title": {
|
||||
"message": "バージョン"
|
||||
},
|
||||
"report.back-to-item": {
|
||||
"message": "{item}に戻る"
|
||||
},
|
||||
"report.checking": {
|
||||
"message": "{item}を確認中…"
|
||||
},
|
||||
"report.could-not-find": {
|
||||
"message": "{item}を見つかりませんでした"
|
||||
},
|
||||
"report.question.content-id": {
|
||||
"message": "{item}のIDは何ですか?"
|
||||
},
|
||||
"scopes.collectionCreate.description": {
|
||||
"message": "コレクションを作成"
|
||||
},
|
||||
"scopes.collectionCreate.label": {
|
||||
"message": "コレクションの作成"
|
||||
},
|
||||
"scopes.collectionDelete.description": {
|
||||
"message": "コレクションを削除"
|
||||
},
|
||||
"scopes.collectionDelete.label": {
|
||||
"message": "コレクションの削除"
|
||||
},
|
||||
"scopes.collectionRead.description": {
|
||||
"message": "コレクションの読み取り"
|
||||
},
|
||||
"scopes.collectionRead.label": {
|
||||
"message": "コレクションの読み取り"
|
||||
},
|
||||
"scopes.collectionWrite.description": {
|
||||
"message": "コレクションへの書き込み"
|
||||
},
|
||||
"scopes.collectionWrite.label": {
|
||||
"message": "コレクションの書き込み"
|
||||
},
|
||||
"scopes.payoutsWrite.description": {
|
||||
"message": "収益を出金する"
|
||||
},
|
||||
"scopes.projectCreate.description": {
|
||||
"message": "新規プロジェクトを作成"
|
||||
},
|
||||
"scopes.projectCreate.label": {
|
||||
"message": "プロジェクトの作成"
|
||||
},
|
||||
"servers.notice.id": {
|
||||
"message": "ID"
|
||||
},
|
||||
"servers.plan.large.name": {
|
||||
"message": "大"
|
||||
},
|
||||
"servers.plan.small.name": {
|
||||
"message": "少"
|
||||
},
|
||||
"settings.billing.payment_method.card_expiry": {
|
||||
"message": "有効期限:{year}年{month}月"
|
||||
},
|
||||
"settings.billing.payment_method_type.jcb": {
|
||||
"message": "JCB"
|
||||
},
|
||||
"settings.display.sidebar.advanced-rendering.description": {
|
||||
"message": "ぼかし効果などの高度なレンダリングを有効にします。グラフィック アクセラレーションが使用できない場合は、パフォーマンスが低下する可能性があります。"
|
||||
},
|
||||
@@ -1739,10 +1943,34 @@
|
||||
"settings.pats.modal.create.action": {
|
||||
"message": "PATを作成"
|
||||
},
|
||||
"settings.pats.modal.create.expires.label": {
|
||||
"message": "有効期限"
|
||||
},
|
||||
"settings.pats.modal.create.name.label": {
|
||||
"message": "名前"
|
||||
},
|
||||
"settings.pats.modal.create.name.placeholder": {
|
||||
"message": "PATの名前を入力..."
|
||||
},
|
||||
"settings.pats.token.never-used": {
|
||||
"message": "未使用"
|
||||
},
|
||||
"settings.profile.bio.title": {
|
||||
"message": "自己紹介"
|
||||
},
|
||||
"settings.sessions.created-ago": {
|
||||
"message": "{ago}に作成"
|
||||
},
|
||||
"settings.sessions.unknown-os": {
|
||||
"message": "不明なOS"
|
||||
},
|
||||
"ui.latest-news-row.latest-news": {
|
||||
"message": "Modrinthの最新ニュース"
|
||||
},
|
||||
"ui.latest-news-row.view-all": {
|
||||
"message": "すべてのニュースを見る"
|
||||
},
|
||||
"version.environment.unknown.title": {
|
||||
"message": "不明な環境"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"message": "제3자 패키지"
|
||||
},
|
||||
"app-marketing.download.title": {
|
||||
"message": "Modrinth App 다운로드 (베타)"
|
||||
"message": "Modrinth App (베타) 다운로드"
|
||||
},
|
||||
"app-marketing.download.windows": {
|
||||
"message": "Windows"
|
||||
@@ -132,7 +132,7 @@
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"app-marketing.features.performance.one-billion-percent": {
|
||||
"message": "10억 %"
|
||||
"message": "10억%"
|
||||
},
|
||||
"app-marketing.features.performance.process-name": {
|
||||
"message": "프로세스 이름"
|
||||
@@ -174,7 +174,7 @@
|
||||
"message": "웹사이트 통합"
|
||||
},
|
||||
"app-marketing.features.youve-used-before": {
|
||||
"message": "이전에 사용했던"
|
||||
"message": "당신이 이전에 사용했던"
|
||||
},
|
||||
"app-marketing.hero.app-screenshot-alt": {
|
||||
"message": "Cobblemon 인스턴스가 ‘콘텐츠’ 페이지에 열려 있는 Modrinth App의 스크린샷"
|
||||
@@ -618,7 +618,7 @@
|
||||
"message": "{formType} 다운로드"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.success": {
|
||||
"message": "{formType} 세금 양식이 성공적으로 제출되었습니다!"
|
||||
"message": "{formType} 세금 신고서가 성공적으로 제출되었습니다!"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.support-text": {
|
||||
"message": "이제 자유롭게 출금할 수 있습니다. 문의 사항이 있거나 정보를 수정해야 한다면 <support-link>고객 지원팀</support-link>에 문의 하세요."
|
||||
@@ -639,10 +639,10 @@
|
||||
"message": "개인입니까, 아니면 외국 법인에 속해 계신가요?"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.header": {
|
||||
"message": "세금 양식"
|
||||
"message": "세금 신고서"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.security.description": {
|
||||
"message": "Modrinth는 세금 양식을 안전하게 수집하고 보관하기 위해 제3자 제공업체인 Track1099를 사용합니다. <security-link>자세히 알아보기</security-link>"
|
||||
"message": "Modrinth는 세금 신고서를 안전하게 수집하고 보관하기 위해 제3자 제공업체인 Track1099를 사용합니다. <security-link>자세히 알아보기</security-link>"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.security.header": {
|
||||
"message": "보안 관행"
|
||||
@@ -651,7 +651,7 @@
|
||||
"message": "미국 시민권자 이신가요?"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.complete-tax-form": {
|
||||
"message": "세금 양식 작성"
|
||||
"message": "세금 신고서 작성"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.continue-with-limit": {
|
||||
"message": "제한된 상태로 계속"
|
||||
@@ -660,16 +660,16 @@
|
||||
"message": "세부 사항"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-amount": {
|
||||
"message": "수량"
|
||||
"message": "금액"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-exchange-rate": {
|
||||
"message": "FX 교환비율"
|
||||
"message": "환율"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-fee": {
|
||||
"message": "수수료"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-gift-card-value": {
|
||||
"message": "기프트 카드 값"
|
||||
"message": "기프트 카드 금액"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-net-amount": {
|
||||
"message": "순수령액"
|
||||
@@ -711,7 +711,7 @@
|
||||
"message": "출금 방법 선택"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.method-selection.tax-limit-warning": {
|
||||
"message": "출금 한도는 <b>{amount}</b>입니다. 추가로 출금하려면 <tax-link>세금 양식을 작성</tax-link>하세요."
|
||||
"message": "출금 한도는 <b>{amount}</b>입니다. 추가로 출금하려면 <tax-link>세금 신고서를 작성</tax-link>하세요."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.account-owner": {
|
||||
"message": "계좌 소유주"
|
||||
@@ -720,7 +720,7 @@
|
||||
"message": "은행 계좌 소유주"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.bank-account-owner-description": {
|
||||
"message": "이 은행 계좌를 운용할 수 있는 권한을 가진 사람의 이름을 입력하세요."
|
||||
"message": "사업체를 대신하여 이 은행 계좌를 운영할 수 있는 권한을 가진 사람의 이름을 입력하세요."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.muralpay-details.coin": {
|
||||
"message": "코인"
|
||||
@@ -762,7 +762,7 @@
|
||||
"message": "네트워크"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.nearing-threshold": {
|
||||
"message": "출금 한도에 거의 도달했습니다. 지금은 <b>{amountRemaining}</b>까지 출금할 수 있지만, 그 이상 출금하려면 세금 양식이 필요합니다."
|
||||
"message": "출금 한도에 거의 도달했습니다. 지금은 <b>{amountRemaining}</b>까지 출금할 수 있지만, 그 이상 출금하려면 세금 신고서가 필요합니다."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.paypal-details.account": {
|
||||
"message": "계정"
|
||||
@@ -813,7 +813,7 @@
|
||||
"message": "인증"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.tax-form": {
|
||||
"message": "세금 양식"
|
||||
"message": "세금 신고서"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.stage.tremendous-details": {
|
||||
"message": "세부 사항"
|
||||
@@ -822,10 +822,10 @@
|
||||
"message": "사용 가능한 잔액 <b>{available}</b> 전액을 출금하려면 아래 양식을 작성해 주세요. 이는 세금 신고를 위해 필요하며, 한 번만 완료하면 됩니다."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tax-form-required.body-with-limit": {
|
||||
"message": "세금 규정을 준수하기 위해 Modrinth의 세무 기록용으로 W-9 또는 W-8 양식을 작성해야 합니다."
|
||||
"message": "세금 규정을 준수하기 위해 Modrinth의 세무 기록용으로 이용하는 W-9 또는 W-8 양식을 작성해야 합니다."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tax-form-required.header": {
|
||||
"message": "세금 양식 필요"
|
||||
"message": "세금 신고서 필요"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.available-denominations-label": {
|
||||
"message": "사용 가능한 금액 단위"
|
||||
@@ -855,10 +855,10 @@
|
||||
"message": "금액 검색"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.select-denomination-hint": {
|
||||
"message": "종교를 선택해 주세요:"
|
||||
"message": "지정 금액을 선택해 주세요:"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.select-denomination-required": {
|
||||
"message": "진행하기 위해 종교를 선택해 주세요"
|
||||
"message": "진행하기 위해 지정 금액을 선택해 주세요"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.unverified-email-header": {
|
||||
"message": "검증되지 않은 이메일"
|
||||
@@ -885,7 +885,7 @@
|
||||
"message": "지금 이용 가능"
|
||||
},
|
||||
"dashboard.revenue.balance": {
|
||||
"message": "잔고"
|
||||
"message": "잔액"
|
||||
},
|
||||
"dashboard.revenue.estimated-tooltip.msg1": {
|
||||
"message": "추정 수익은 제공될 때까지 변경될 수 있습니다."
|
||||
@@ -894,7 +894,7 @@
|
||||
"message": "Modrinth가 수익을 처리하는 방법에 대해 알아보려면 클릭하세요."
|
||||
},
|
||||
"dashboard.revenue.estimated-with-date": {
|
||||
"message": ""
|
||||
"message": "예정일: {date}"
|
||||
},
|
||||
"dashboard.revenue.processing": {
|
||||
"message": "처리 중"
|
||||
@@ -903,13 +903,13 @@
|
||||
"message": "수익은 월말까지 처리 중 상태로 유지되며, 이후 60일 후에 사용 가능해집니다."
|
||||
},
|
||||
"dashboard.revenue.stats.received": {
|
||||
"message": ""
|
||||
"message": "수령 완료"
|
||||
},
|
||||
"dashboard.revenue.stats.transactions": {
|
||||
"message": "거래"
|
||||
},
|
||||
"dashboard.revenue.stats.withdrawn": {
|
||||
"message": "출금됨"
|
||||
"message": "출금 완료"
|
||||
},
|
||||
"dashboard.revenue.tos": {
|
||||
"message": "Modrinth에 프로젝트를 업로드하고 계좌에서 자금을 출금함으로써 귀하는 당사의 <terms-link>리워드 프로그램 약관</terms-link>에 동의하는 것으로 간주됩니다. <info-link>리워드 프로그램</info-link>에 대해 자세히 알아보세요."
|
||||
@@ -918,19 +918,19 @@
|
||||
"message": "CSV로 다운로드"
|
||||
},
|
||||
"dashboard.revenue.transactions.header": {
|
||||
"message": ""
|
||||
"message": "거래 기록"
|
||||
},
|
||||
"dashboard.revenue.transactions.none": {
|
||||
"message": ""
|
||||
"message": "거래 기록 없음"
|
||||
},
|
||||
"dashboard.revenue.transactions.none.desc": {
|
||||
"message": "귀하의 지급 및 출금 내역이 여기에 표시됩니다."
|
||||
"message": "귀하의 결제 및 출금 내역이 여기에 표시됩니다."
|
||||
},
|
||||
"dashboard.revenue.transactions.see-all": {
|
||||
"message": "모두 보기"
|
||||
},
|
||||
"dashboard.revenue.withdraw.blocked-tin-mismatch": {
|
||||
"message": "귀하의 출금이 일시적으로 잠겼습니다. 귀하의 TIN 또는 SSN이 국세청(IRS) 기록과 일치하지 않았기 때문입니다. 세금 양식을 재설정하고 재제출하려면 고객지원팀에 문의해 주세요."
|
||||
"message": "귀하의 출금이 일시적으로 제한되었습니다. 귀하의 TIN 또는 SSN이 국세청(IRS) 기록과 일치하지 않았기 때문입니다. 세금 신고서를 다시 작성하고 재제출하려면 고객지원팀에 문의해 주세요."
|
||||
},
|
||||
"dashboard.revenue.withdraw.card.description": {
|
||||
"message": "사용 가능한 잔액에서 원하는 출금 방법으로 출금하세요."
|
||||
@@ -945,7 +945,7 @@
|
||||
"message": "계정"
|
||||
},
|
||||
"dashboard.withdraw.completion.amount": {
|
||||
"message": ""
|
||||
"message": "금액"
|
||||
},
|
||||
"dashboard.withdraw.completion.close-button": {
|
||||
"message": "닫기"
|
||||
@@ -963,19 +963,19 @@
|
||||
"message": "수수료"
|
||||
},
|
||||
"dashboard.withdraw.completion.method": {
|
||||
"message": ""
|
||||
"message": "수단"
|
||||
},
|
||||
"dashboard.withdraw.completion.net-amount": {
|
||||
"message": ""
|
||||
"message": "순수령액"
|
||||
},
|
||||
"dashboard.withdraw.completion.recipient": {
|
||||
"message": ""
|
||||
"message": "수취인"
|
||||
},
|
||||
"dashboard.withdraw.completion.title": {
|
||||
"message": ""
|
||||
"message": "출금 완료"
|
||||
},
|
||||
"dashboard.withdraw.completion.transactions-button": {
|
||||
"message": ""
|
||||
"message": "거래 기록"
|
||||
},
|
||||
"dashboard.withdraw.completion.wallet": {
|
||||
"message": "지갑"
|
||||
@@ -996,13 +996,13 @@
|
||||
"message": "출금 요청을 제출할 수 없습니다. 세부 정보를 확인하거나 고객 지원팀에 문의해 주세요.\n{error}"
|
||||
},
|
||||
"dashboard.withdraw.error.generic.title": {
|
||||
"message": ""
|
||||
"message": "출금 실패"
|
||||
},
|
||||
"dashboard.withdraw.error.insufficient-balance.text": {
|
||||
"message": "이 출금을 진행하기에는 잔액이 부족합니다."
|
||||
},
|
||||
"dashboard.withdraw.error.insufficient-balance.title": {
|
||||
"message": ""
|
||||
"message": "잔액 부족"
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-address.text": {
|
||||
"message": "제공하신 주소를 확인할 수 없습니다. 주소 정보를 다시 한번 확인해 주세요."
|
||||
@@ -1011,22 +1011,22 @@
|
||||
"message": "주소 확인에 실패했습니다"
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-bank.text": {
|
||||
"message": "제공하신 은행 계좌 정보가 유효하지 않습니다. 정보를 다시 한 번 확인해 주세요."
|
||||
"message": "제공하신 은행 계좌 정보가 유효하지 않습니다. 정보를 다시 한번 확인해 주세요."
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-bank.title": {
|
||||
"message": ""
|
||||
"message": "잘못된 은행 정보"
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-wallet.text": {
|
||||
"message": "제공하신 암호화폐 지갑 주소가 유효하지 않습니다. 다시 한번 확인하신 후 재시도해 주세요."
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-wallet.title": {
|
||||
"message": ""
|
||||
"message": "잘못된 지갑 주소"
|
||||
},
|
||||
"dashboard.withdraw.error.minimum-not-met.text": {
|
||||
"message": "출금 금액(수수료 차감 후)이 최소 요건을 충족하지 않습니다. 출금 금액을 늘려 주세요."
|
||||
},
|
||||
"dashboard.withdraw.error.minimum-not-met.title": {
|
||||
"message": ""
|
||||
"message": "금액이 너무 적습니다"
|
||||
},
|
||||
"dashboard.withdraw.error.paypal-country-mismatch.text": {
|
||||
"message": "귀하의 지역에 맞는 올바른 PayPal 송금 옵션을 사용해 주세요."
|
||||
@@ -1035,10 +1035,10 @@
|
||||
"message": "PayPal 지역 불일치"
|
||||
},
|
||||
"dashboard.withdraw.error.tax-form.text": {
|
||||
"message": "출금 요청을 제출하려면 세금 양식을 작성해야 합니다."
|
||||
"message": "출금 요청을 제출하려면 세금 신고서를 작성해야 합니다."
|
||||
},
|
||||
"dashboard.withdraw.error.tax-form.title": {
|
||||
"message": "세금 양식을 작성해 주세요"
|
||||
"message": "세금 신고서를 작성해 주세요"
|
||||
},
|
||||
"error.collection.404.list_item.1": {
|
||||
"message": "컬렉션 URL을 잘못 입력했을 수 있습니다."
|
||||
@@ -1068,7 +1068,7 @@
|
||||
"message": "법적 사유로 인해 콘텐츠를 이용하실 수 없습니다"
|
||||
},
|
||||
"error.generic.default.list_item.1": {
|
||||
"message": "<status-link>서버 상태</status-link>에서 Modrinth 서버가 다운되었는지 확인해보세요."
|
||||
"message": "<status-link>서버 상태</status-link>에서 Modrinth 서버가 다운되었는지 확인해 보세요."
|
||||
},
|
||||
"error.generic.default.list_item.2": {
|
||||
"message": "계속 문제가 발생하는 경우, <discord-link>Discord 서버</discord-link>에 접속하여 Modrinth 팀과 연락할 수 있습니다."
|
||||
@@ -1152,7 +1152,7 @@
|
||||
"message": "Modrinth 팀의 지속적인 업데이트로 최고의 모딩 경험을 누리세요"
|
||||
},
|
||||
"landing.creator.feature.constantly-evolving.title": {
|
||||
"message": ""
|
||||
"message": "지속적인 발전"
|
||||
},
|
||||
"landing.creator.feature.data-statistics.description": {
|
||||
"message": "페이지 뷰, 다운로드 횟수 및 수익에 대한 상세한 보고서를 확인하세요"
|
||||
@@ -1164,13 +1164,13 @@
|
||||
"message": "검색, 홈페이지, 디스코드 서버 등을 통해 수천 명의 사용자에게 당신의 프로젝트를 알릴 수 있습니다! 앞으로 더 많은 방법이 추가될 예정이니 기대해 주세요!"
|
||||
},
|
||||
"landing.creator.feature.discovery.title": {
|
||||
"message": "발견"
|
||||
"message": "탐색"
|
||||
},
|
||||
"landing.creator.feature.diverse-ecosystem.description": {
|
||||
"message": "Minotaur를 통해 빌드 도구와 통합하여 새 버전을 출시하는 즉시 자동 업로드가 이루어지도록 하세요"
|
||||
},
|
||||
"landing.creator.feature.diverse-ecosystem.title": {
|
||||
"message": ""
|
||||
"message": "다양한 생태계"
|
||||
},
|
||||
"landing.creator.feature.monetization.description": {
|
||||
"message": "프로젝트 페이지에서 광고 수익을 얻고 언제든지 자금을 출금하세요"
|
||||
@@ -1194,7 +1194,7 @@
|
||||
"message": "좋아하는 프로젝트를 팔로우하세요"
|
||||
},
|
||||
"landing.feature.launcher.description": {
|
||||
"message": "Modrinth의 오픈소스 API를 통해 런처는 Modrinth와의 심층 연동을 추가할 수 있습니다. <link>자체 앱</link>과 ATLauncher, MultiMC, Prism Launcher 등 인기 런처를 통해 Modrinth를 사용할 수 있습니다."
|
||||
"message": "Modrinth의 오픈소스 API를 통해 런처는 Modrinth와의 심층 연동을 지원할 수 있습니다. <link>자체 앱</link>과 ATLauncher, MultiMC, Prism Launcher 등 인기 런처를 통해 Modrinth를 사용할 수 있습니다."
|
||||
},
|
||||
"landing.feature.launcher.heading": {
|
||||
"message": "원하는 런처로 플레이하세요"
|
||||
@@ -1248,7 +1248,7 @@
|
||||
"message": "알림"
|
||||
},
|
||||
"landing.notifications.received-time": {
|
||||
"message": ""
|
||||
"message": "수신: {time}"
|
||||
},
|
||||
"landing.notifications.version-released": {
|
||||
"message": "{version} 버전이 {gameVersion}용으로 출시되었습니다"
|
||||
@@ -1263,10 +1263,10 @@
|
||||
"message": "정렬 기준"
|
||||
},
|
||||
"landing.section.for-creators.description": {
|
||||
"message": ""
|
||||
"message": "여러분의 창작물에 온라인 공간을 마련하고 열성적인 플레이어들에게 다가갈 수 있습니다."
|
||||
},
|
||||
"landing.section.for-creators.label": {
|
||||
"message": ""
|
||||
"message": "창작자를 위해"
|
||||
},
|
||||
"landing.section.for-creators.tagline": {
|
||||
"message": "당신의 콘텐츠를 전 세계와 공유하세요"
|
||||
@@ -1275,7 +1275,7 @@
|
||||
"message": "마법 같은 생물군계부터 저주받은 던전까지, 게임 플레이를 한 단계 업그레이드할 콘텐츠를 반드시 발견할 수 있을 것입니다."
|
||||
},
|
||||
"landing.section.for-players.label": {
|
||||
"message": ""
|
||||
"message": "플레이어를 위해"
|
||||
},
|
||||
"landing.section.for-players.tagline": {
|
||||
"message": "{count}개 이상의 창작물을 찾으세요"
|
||||
@@ -1290,16 +1290,16 @@
|
||||
"message": "새로 만들기..."
|
||||
},
|
||||
"layout.action.file-lookup": {
|
||||
"message": ""
|
||||
"message": "파일 찾기"
|
||||
},
|
||||
"layout.action.lookup-by-email": {
|
||||
"message": ""
|
||||
"message": "이메일로 찾기"
|
||||
},
|
||||
"layout.action.manage-affiliates": {
|
||||
"message": ""
|
||||
"message": "제휴 링크 관리"
|
||||
},
|
||||
"layout.action.manage-server-notices": {
|
||||
"message": ""
|
||||
"message": "서버 알림 관리"
|
||||
},
|
||||
"layout.action.new-collection": {
|
||||
"message": "새 컬렉션"
|
||||
@@ -1311,13 +1311,13 @@
|
||||
"message": "새 프로젝트"
|
||||
},
|
||||
"layout.action.reports": {
|
||||
"message": ""
|
||||
"message": "보고서 검토"
|
||||
},
|
||||
"layout.action.review-projects": {
|
||||
"message": ""
|
||||
"message": "프로젝트 검토"
|
||||
},
|
||||
"layout.action.tech-review": {
|
||||
"message": ""
|
||||
"message": "기술 검토"
|
||||
},
|
||||
"layout.avatar.alt": {
|
||||
"message": "당신의 아바타"
|
||||
@@ -1337,38 +1337,44 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "빌드 중 API에서 상태 생성 오류가 발생했습니다."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "공식 Modrinth 웹사이트에 접속하려는 경우 <link>https://modrinth.com</link>를 방문하십시오. 이 미리보기 배포판은 Modrinth 직원이 테스트 목적으로 사용합니다. 이는 <branch-link>{owner}/{branch}</branch-link> @ {commit}을 사용하여 구축되었습니다."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "이 웹사이트는 Modrinth의 미리보기입니다."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "스테이징 환경은 프로덕션 Modrinth 데이터베이스와 완전히 분리되어 있습니다. 이는 테스트 및 디버깅 목적으로 사용되며, 프로덕션 인스턴스보다 최신 버전의 Modrinth 백엔드 또는 프론트엔드 개발 버전이 실행될 수 있습니다."
|
||||
},
|
||||
"layout.banner.staging.title": {
|
||||
"message": "현재 모드린스의 스테이징 환경을 보고 있습니다"
|
||||
"message": "현재 Modrinth의 스테이징 환경을 보고 있습니다"
|
||||
},
|
||||
"layout.banner.subscription-payment-failed.button": {
|
||||
"message": "결제 정보 갱신"
|
||||
"message": "결제 정보 업데이트"
|
||||
},
|
||||
"layout.banner.subscription-payment-failed.description": {
|
||||
"message": "하나 이상의 구독이 갱신되지 않았습니다. 액세스 권한을 잃지 않도록 결제 수단을 갱신해 주세요!"
|
||||
"message": "하나 이상의 구독이 갱신되지 않았습니다. 액세스 권한을 잃지 않도록 결제 수단을 업데이트해 주세요!"
|
||||
},
|
||||
"layout.banner.subscription-payment-failed.title": {
|
||||
"message": ""
|
||||
"message": "청구 조치가 필요합니다."
|
||||
},
|
||||
"layout.banner.tax.action": {
|
||||
"message": ""
|
||||
"message": "세금 신고서를 작성 완료하세요"
|
||||
},
|
||||
"layout.banner.tax.description": {
|
||||
"message": "올해 이미 Modrinth에서 600달러 이상을 출금하셨습니다. 세금 규정을 준수하기 위해 세금 신고서를 작성하셔야 합니다. 이 서류가 제출될 때까지 출금이 일시 중단됩니다."
|
||||
},
|
||||
"layout.banner.tax.title": {
|
||||
"message": ""
|
||||
"message": "세금 신고서 필요"
|
||||
},
|
||||
"layout.banner.tin-mismatch.action": {
|
||||
"message": "지원 문의"
|
||||
},
|
||||
"layout.banner.tin-mismatch.description": {
|
||||
"message": "귀하의 출금이 일시적으로 제한되었습니다. 귀하의 TIN 또는 SSN이 국세청(IRS) 기록과 일치하지 않기 때문입니다. 세금 양식을 재설정하고 재제출하려면 고객지원팀에 문의해 주십시오."
|
||||
"message": "귀하의 출금이 일시적으로 제한되었습니다. 귀하의 TIN 또는 SSN이 국세청(IRS) 기록과 일치하지 않았기 때문입니다. 세금 신고서를 다시 작성하고 재제출하려면 고객지원팀에 문의해 주십시오."
|
||||
},
|
||||
"layout.banner.tin-mismatch.title": {
|
||||
"message": ""
|
||||
"message": "세금 신고서 제출 실패"
|
||||
},
|
||||
"layout.banner.verify-email.action": {
|
||||
"message": "인증 이메일 재전송"
|
||||
@@ -1425,7 +1431,7 @@
|
||||
"message": "상품"
|
||||
},
|
||||
"layout.footer.products.app": {
|
||||
"message": "Modrinth 앱"
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"layout.footer.products.plus": {
|
||||
"message": "Modrinth+"
|
||||
@@ -1479,7 +1485,7 @@
|
||||
"message": "메뉴 열기"
|
||||
},
|
||||
"layout.nav.active-reports": {
|
||||
"message": "활성 보고"
|
||||
"message": "활성된 신고"
|
||||
},
|
||||
"layout.nav.analytics": {
|
||||
"message": "통계"
|
||||
@@ -1647,7 +1653,7 @@
|
||||
"message": "계좌 번호"
|
||||
},
|
||||
"muralpay.field.branch-code": {
|
||||
"message": "브렌치 코드"
|
||||
"message": "은행 코드"
|
||||
},
|
||||
"muralpay.field.clabe": {
|
||||
"message": "CLABE"
|
||||
@@ -1740,7 +1746,7 @@
|
||||
"message": "계좌 번호 입력"
|
||||
},
|
||||
"muralpay.placeholder.enter-branch-code": {
|
||||
"message": "브랜치 코드 입력"
|
||||
"message": "은행 코드 입력"
|
||||
},
|
||||
"muralpay.placeholder.enter-clabe": {
|
||||
"message": "18자리 CLABE 입력"
|
||||
@@ -1818,16 +1824,16 @@
|
||||
"message": "Crypto (USDC)"
|
||||
},
|
||||
"muralpay.warning.wallet-address": {
|
||||
"message": "지갑 주소를 다시 확인하세요. 잘못된 주소로 송금된 자금은 회수할 수 없습니다."
|
||||
"message": "지갑 주소를 다시 확인하세요. 잘못된 주소로 송금된 자금은 복구할 수 없습니다."
|
||||
},
|
||||
"profile.bio.fallback.creator": {
|
||||
"message": "Modrinth의 창작자입니다."
|
||||
"message": "Modrinth 창작자입니다."
|
||||
},
|
||||
"profile.bio.fallback.user": {
|
||||
"message": "Modrinth의 사용자입니다."
|
||||
"message": "Modrinth 사용자입니다."
|
||||
},
|
||||
"profile.button.billing": {
|
||||
"message": ""
|
||||
"message": "사용자 청구 관리"
|
||||
},
|
||||
"profile.button.edit-role": {
|
||||
"message": "역할 수정"
|
||||
@@ -1839,10 +1845,10 @@
|
||||
"message": "프로젝트 관리"
|
||||
},
|
||||
"profile.button.remove-affiliate": {
|
||||
"message": ""
|
||||
"message": "제휴 취소"
|
||||
},
|
||||
"profile.button.set-affiliate": {
|
||||
"message": ""
|
||||
"message": "제휴사로 설정"
|
||||
},
|
||||
"profile.details.label.auth-providers": {
|
||||
"message": "인증 제공자"
|
||||
@@ -1971,7 +1977,7 @@
|
||||
"message": "검토를 위해 다시 제출"
|
||||
},
|
||||
"project-moderation-nags.resubmit-for-review-desc": {
|
||||
"message": ""
|
||||
"message": "귀하의 프로젝트는 Modrinth 직원들에 의해 {status, select, rejected {거부} withheld {보류} other {{status}}} 처리되었습니다. 대부분의 경우, 직원들의 메시지를 해결한 후 재검토를 위해 재제출할 수 있습니다."
|
||||
},
|
||||
"project-moderation-nags.submit-checklist-tooltip": {
|
||||
"message": "게시 체크리스트에 필요한 단계를 완료해야 합니다!"
|
||||
@@ -2043,22 +2049,22 @@
|
||||
"message": "셰이더"
|
||||
},
|
||||
"project.about.details.created": {
|
||||
"message": "{date} 전에 생성됨"
|
||||
"message": "{date} 에 생성됨"
|
||||
},
|
||||
"project.about.details.licensed": {
|
||||
"message": "{license} 라이선스"
|
||||
},
|
||||
"project.about.details.published": {
|
||||
"message": "{date} 전에 게시됨"
|
||||
"message": "{date} 에 게시됨"
|
||||
},
|
||||
"project.about.details.submitted": {
|
||||
"message": "{date} 전에 제출됨"
|
||||
"message": "{date} 에 제출됨"
|
||||
},
|
||||
"project.about.details.title": {
|
||||
"message": "세부 사항"
|
||||
},
|
||||
"project.about.details.updated": {
|
||||
"message": "{date} 전에 업데이트됨"
|
||||
"message": "{date} 에 업데이트됨"
|
||||
},
|
||||
"project.actions.create-server": {
|
||||
"message": "서버 만들기"
|
||||
@@ -2112,7 +2118,7 @@
|
||||
"message": "Modrinth App을 설치하지 않으셨나요?"
|
||||
},
|
||||
"project.download.no-versions-available": {
|
||||
"message": ""
|
||||
"message": "{gameVersion} 및 {platform}용 버전이 없습니다."
|
||||
},
|
||||
"project.download.platform": {
|
||||
"message": "플랫폼: {platform}"
|
||||
@@ -2145,25 +2151,25 @@
|
||||
"message": "{title} 다운로드"
|
||||
},
|
||||
"project.environment.migration-no-permission.message": {
|
||||
"message": ""
|
||||
"message": "Modrinth의 환경 시스템을 전면 개편하여 새로운 옵션이 추가되었습니다. 해당 설정 변경 권한은 없으나, 환경 메타데이터 검토가 필요함을 프로젝트 다른 구성원에게 알려주시기 바랍니다."
|
||||
},
|
||||
"project.environment.migration-no-permission.title": {
|
||||
"message": ""
|
||||
"message": "환경 메타데이터를 검토해야 합니다"
|
||||
},
|
||||
"project.environment.migration.learn-more": {
|
||||
"message": "변경 사항에 대해 자세히 알아보기"
|
||||
},
|
||||
"project.environment.migration.message": {
|
||||
"message": ""
|
||||
"message": "Modrinth의 환경 시스템을 대대적으로 개편하여 새로운 옵션이 추가되었습니다. 메타데이터가 올바른지 확인해 주시기 바랍니다."
|
||||
},
|
||||
"project.environment.migration.review-button": {
|
||||
"message": ""
|
||||
"message": "환경 설정 검토"
|
||||
},
|
||||
"project.environment.migration.title": {
|
||||
"message": ""
|
||||
"message": "환경 메타데이터를 검토해 주세요"
|
||||
},
|
||||
"project.error.loading": {
|
||||
"message": ""
|
||||
"message": "프로젝트 데이터 불러오기 중 오류 {message}"
|
||||
},
|
||||
"project.error.page-not-found": {
|
||||
"message": "페이지를 찾을 수 없습니다"
|
||||
@@ -2175,13 +2181,13 @@
|
||||
"message": "갤러리"
|
||||
},
|
||||
"project.license.error": {
|
||||
"message": "이용 허가를 불러올 수 없습니다."
|
||||
"message": "라이선스 텍스트를 불러올 수 없습니다."
|
||||
},
|
||||
"project.license.loading": {
|
||||
"message": "이용 하거를 불러오는 중..."
|
||||
"message": "라이선스 텍스트를 불러오는 중..."
|
||||
},
|
||||
"project.license.title": {
|
||||
"message": "이용 허가"
|
||||
"message": "라이선스"
|
||||
},
|
||||
"project.moderation.title": {
|
||||
"message": "관리"
|
||||
@@ -2202,7 +2208,7 @@
|
||||
"message": "프로젝트 수정됨"
|
||||
},
|
||||
"project.settings.general.name.description": {
|
||||
"message": ""
|
||||
"message": "접두사, 접미사, 괄호 속 설명 또는 추가 설명은 피하세요—프로젝트의 실제 이름만 기재하세요."
|
||||
},
|
||||
"project.settings.general.name.placeholder.1": {
|
||||
"message": "예시: Nether Overhaul 2"
|
||||
@@ -2226,22 +2232,22 @@
|
||||
"message": "프로젝트를 한 문장으로 요약하세요."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.1": {
|
||||
"message": ""
|
||||
"message": "예시: 게임 진행을 네더를 중심으로 재구성합니다."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.2": {
|
||||
"message": ""
|
||||
"message": "예시: 착용 가능한 건설 장비 추가."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.3": {
|
||||
"message": ""
|
||||
"message": "예시: 현실적인 갱도 건설 메커니즘을 추가합니다."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.4": {
|
||||
"message": ""
|
||||
"message": "예시: 네더 포탈 간의 연결 방식을 개선합니다."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.5": {
|
||||
"message": ""
|
||||
"message": "예시: 밤에 마주칠 수 있는 일반 몬스터들의 강력한 보스 버전을 추가합니다."
|
||||
},
|
||||
"project.settings.general.tagline.title": {
|
||||
"message": ""
|
||||
"message": "요약"
|
||||
},
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
@@ -2253,13 +2259,13 @@
|
||||
"message": "프로젝트 대시보드 방문"
|
||||
},
|
||||
"project.stats.downloads-label": {
|
||||
"message": "다운로드{count, plural, other {}}"
|
||||
"message": "다운로드"
|
||||
},
|
||||
"project.stats.followers-label": {
|
||||
"message": "팔로워{count, plural, other {}}"
|
||||
"message": "팔로워"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} 은(는) 보관되었습니다. 더 이상 업데이트가 제공되지 않습니다."
|
||||
"message": "{title} 은(는) 보관되었습니다. 작성자가 프로젝트 보관 해제를 결정하지 않는 한 더 이상 업데이트가 제공되지 않습니다."
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "버전"
|
||||
@@ -2274,7 +2280,7 @@
|
||||
"message": "{item}(으)로 돌아가기"
|
||||
},
|
||||
"report.body.description": {
|
||||
"message": "가능하면 링크와 이미지를 포함하고 관련성이 있다면 알려주세요. 비어 있거나 성의없는 신고는 무시됩니다."
|
||||
"message": "가능하면 링크와 이미지를 포함하고 관련성이 있다면 알려주세요. 비어 있거나 성의 없는 신고는 무시됩니다."
|
||||
},
|
||||
"report.body.title": {
|
||||
"message": "신고에 대한 추가 정황을 제공해 주세요"
|
||||
@@ -2352,7 +2358,7 @@
|
||||
"message": "컬렉션 생성"
|
||||
},
|
||||
"scopes.collectionCreate.label": {
|
||||
"message": "컬렉션 생성하기"
|
||||
"message": "컬렉션 생성"
|
||||
},
|
||||
"scopes.collectionDelete.description": {
|
||||
"message": "컬렉션 삭제"
|
||||
@@ -2385,7 +2391,7 @@
|
||||
"message": "알림 쓰기"
|
||||
},
|
||||
"scopes.organizationCreate.description": {
|
||||
"message": "조직 생성하기"
|
||||
"message": "조직 생성"
|
||||
},
|
||||
"scopes.organizationCreate.label": {
|
||||
"message": "조직 생성"
|
||||
@@ -2433,22 +2439,22 @@
|
||||
"message": "PAT 쓰기"
|
||||
},
|
||||
"scopes.payoutsRead.description": {
|
||||
"message": "인출 데이터 확인하기"
|
||||
"message": "거래 데이터 확인하기"
|
||||
},
|
||||
"scopes.payoutsRead.label": {
|
||||
"message": "지불 내역 읽기"
|
||||
"message": "거래 내역 읽기"
|
||||
},
|
||||
"scopes.payoutsWrite.description": {
|
||||
"message": "자금 인출하기"
|
||||
"message": "자금 출금하기"
|
||||
},
|
||||
"scopes.payoutsWrite.label": {
|
||||
"message": "지불 내역 쓰기"
|
||||
"message": "거래 내역 쓰기"
|
||||
},
|
||||
"scopes.performAnalytics.description": {
|
||||
"message": "분석 수행하기"
|
||||
},
|
||||
"scopes.performAnalytics.label": {
|
||||
"message": "분석 수행"
|
||||
"message": "분석 수행하기"
|
||||
},
|
||||
"scopes.projectCreate.description": {
|
||||
"message": "새 프로젝트 생성"
|
||||
@@ -2457,7 +2463,7 @@
|
||||
"message": "프로젝트 생성"
|
||||
},
|
||||
"scopes.projectDelete.description": {
|
||||
"message": "프로젝트 삭제하기"
|
||||
"message": "프로젝트 삭제"
|
||||
},
|
||||
"scopes.projectDelete.label": {
|
||||
"message": "프로젝트 삭제"
|
||||
@@ -2526,7 +2532,7 @@
|
||||
"message": "스레드 쓰기"
|
||||
},
|
||||
"scopes.threadWrite.label": {
|
||||
"message": "스레드 작성"
|
||||
"message": "스레드 쓰기"
|
||||
},
|
||||
"scopes.userAuthWrite.description": {
|
||||
"message": "로그인 정보 수정"
|
||||
@@ -2565,7 +2571,7 @@
|
||||
"message": "버전 생성"
|
||||
},
|
||||
"scopes.versionDelete.description": {
|
||||
"message": "버전 삭제하기"
|
||||
"message": "버전 삭제"
|
||||
},
|
||||
"scopes.versionDelete.label": {
|
||||
"message": "버전 삭제"
|
||||
@@ -2601,7 +2607,7 @@
|
||||
"message": "활동"
|
||||
},
|
||||
"servers.notice.begins": {
|
||||
"message": ""
|
||||
"message": "시작"
|
||||
},
|
||||
"servers.notice.dismissable": {
|
||||
"message": "무시 가능"
|
||||
@@ -2631,7 +2637,7 @@
|
||||
"message": "대형"
|
||||
},
|
||||
"servers.plan.medium.description": {
|
||||
"message": "6–15명의 플레이어와 여러개의 모드에 적합합니다."
|
||||
"message": "6–15명의 플레이어와 여러 개의 모드에 적합합니다."
|
||||
},
|
||||
"servers.plan.medium.name": {
|
||||
"message": "중형"
|
||||
@@ -2655,7 +2661,7 @@
|
||||
"message": "이 결제 수단 제거"
|
||||
},
|
||||
"settings.billing.modal.delete.description": {
|
||||
"message": "결제 수단이 영원히 제거됩니다 (정말 영원히)."
|
||||
"message": "결제 수단이 영구적으로 제거됩니다."
|
||||
},
|
||||
"settings.billing.modal.delete.title": {
|
||||
"message": "정말 이 결제 수단을 제거하시겠습니까?"
|
||||
@@ -2697,7 +2703,7 @@
|
||||
"message": "Diners Club"
|
||||
},
|
||||
"settings.billing.payment_method_type.discover": {
|
||||
"message": "Discover"
|
||||
"message": "탐색"
|
||||
},
|
||||
"settings.billing.payment_method_type.eftpos": {
|
||||
"message": "EFTPOS"
|
||||
@@ -2721,10 +2727,10 @@
|
||||
"message": "Visa"
|
||||
},
|
||||
"settings.billing.pyro_subscription.description": {
|
||||
"message": "당신의 Modrinth 서버 구독을 관리하세요."
|
||||
"message": "당신의 Modrinth Hosting 구독을 관리하세요."
|
||||
},
|
||||
"settings.billing.pyro_subscription.title": {
|
||||
"message": "Modrinth 서버 구독"
|
||||
"message": "Modrinth Hosting 구독"
|
||||
},
|
||||
"settings.billing.subscription.description": {
|
||||
"message": "당신의 Modrinth 구독을 관리하세요."
|
||||
@@ -2802,7 +2808,7 @@
|
||||
"message": "필터 사이드바를 검색 결과 페이지의 오른쪽에 정렬합니다."
|
||||
},
|
||||
"settings.display.sidebar.right-aligned-filters-sidebar.title": {
|
||||
"message": "검색 페이지에서 필터 사이트바를 오른쪽으로 정렬"
|
||||
"message": "검색 페이지에서 필터 사이드바를 오른쪽으로 정렬"
|
||||
},
|
||||
"settings.display.theme.description": {
|
||||
"message": "이 디바이스에서 선호하는 Modrinth의 색상 테마를 선택합니다."
|
||||
@@ -2835,7 +2841,7 @@
|
||||
"message": "이 토큰 삭제"
|
||||
},
|
||||
"settings.pats.modal.delete.description": {
|
||||
"message": "토큰이 영원히 삭제됩니다 (정말 영원히)."
|
||||
"message": "토큰이 영구적으로 삭제됩니다."
|
||||
},
|
||||
"settings.pats.modal.delete.title": {
|
||||
"message": "정말로 이 토큰을 삭제할까요?"
|
||||
@@ -2850,13 +2856,13 @@
|
||||
"message": "토큰 해지"
|
||||
},
|
||||
"settings.pats.token.expired-ago": {
|
||||
"message": "{ago} 전에 만료됨"
|
||||
"message": "{ago} 에 만료됨"
|
||||
},
|
||||
"settings.pats.token.expires-in": {
|
||||
"message": "{inTime} 후에 만료"
|
||||
},
|
||||
"settings.pats.token.last-used": {
|
||||
"message": "{ago} 전에 사용됨"
|
||||
"message": "{ago} 에 사용됨"
|
||||
},
|
||||
"settings.pats.token.never-used": {
|
||||
"message": "사용된 적 없음"
|
||||
@@ -2880,16 +2886,16 @@
|
||||
"message": "프로필 사진"
|
||||
},
|
||||
"settings.profile.username.description": {
|
||||
"message": "대소문자 구분 없이 프로필을 식별할 수 있는 이름."
|
||||
"message": "대소문자 구분 없이 프로필을 식별할 수 있는 이름"
|
||||
},
|
||||
"settings.profile.username.title": {
|
||||
"message": "사용자명"
|
||||
},
|
||||
"settings.sessions.action.revoke-session": {
|
||||
"message": "세션 해지"
|
||||
"message": "세션 제거"
|
||||
},
|
||||
"settings.sessions.created-ago": {
|
||||
"message": "{ago} 전에 생성됨"
|
||||
"message": "{ago} 에 생성됨"
|
||||
},
|
||||
"settings.sessions.current-session": {
|
||||
"message": "현재 세션"
|
||||
@@ -2898,7 +2904,7 @@
|
||||
"message": "현재 Modrinth 계정으로 로그인 중인 장치 목록입니다. 여기서 각 장치를 개별적으로 로그아웃시키실 수 있습니다.\n\n만약 확인되지 않는 장치가 있다면 장치를 로그아웃시키신 후 즉시 Modrinth 계정 비밀번호를 변경하세요."
|
||||
},
|
||||
"settings.sessions.last-accessed-ago": {
|
||||
"message": "{ago} 전에 접근함"
|
||||
"message": "{ago} 에 접근함"
|
||||
},
|
||||
"settings.sessions.unknown-os": {
|
||||
"message": "알 수 없는 운영체제"
|
||||
@@ -2913,15 +2919,15 @@
|
||||
"message": "모든 뉴스 보기"
|
||||
},
|
||||
"version.environment.none.description": {
|
||||
"message": ""
|
||||
"message": "이 버전의 환경이 지정되지 않았습니다."
|
||||
},
|
||||
"version.environment.none.title": {
|
||||
"message": ""
|
||||
"message": "설정된 환경 없음"
|
||||
},
|
||||
"version.environment.unknown.description": {
|
||||
"message": ""
|
||||
"message": "환경: “{environment}”이(가) 인식되지 않습니다."
|
||||
},
|
||||
"version.environment.unknown.title": {
|
||||
"message": ""
|
||||
"message": "알 수 없는 환경"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,9 +998,18 @@
|
||||
"dashboard.withdraw.error.invalid-bank.title": {
|
||||
"message": "Ongeldige bankgegevens"
|
||||
},
|
||||
"dashboard.withdraw.error.invalid-wallet.title": {
|
||||
"message": "Ongeldig portemonnee adres"
|
||||
},
|
||||
"dashboard.withdraw.error.minimum-not-met.title": {
|
||||
"message": "Te laag bedrag"
|
||||
},
|
||||
"dashboard.withdraw.error.paypal-country-mismatch.title": {
|
||||
"message": "PayPal regio mismatch"
|
||||
},
|
||||
"dashboard.withdraw.error.tax-form.title": {
|
||||
"message": "Gelieve het belastingformulier in te vullen"
|
||||
},
|
||||
"error.collection.404.list_item.1": {
|
||||
"message": "U heeft mogelijk de URL van de collectie fout ingetypt."
|
||||
},
|
||||
@@ -1400,6 +1409,9 @@
|
||||
"layout.meta.description": {
|
||||
"message": "Download Minecraft-mods, plugins datapacks, shaders resourcepacks op Modrinth. Ontdek en publiceer projecten op Modrinth met een modern en makkelijk te gebruiken interface en API."
|
||||
},
|
||||
"layout.meta.og-description": {
|
||||
"message": "Ontdek en publiceer Minecraft content!"
|
||||
},
|
||||
"layout.mobile.close-menu": {
|
||||
"message": "Sluit menu"
|
||||
},
|
||||
@@ -1445,6 +1457,15 @@
|
||||
"layout.nav.profile": {
|
||||
"message": "Profiel"
|
||||
},
|
||||
"layout.nav.projects": {
|
||||
"message": "Projecten"
|
||||
},
|
||||
"layout.nav.revenue": {
|
||||
"message": "Inkomsten"
|
||||
},
|
||||
"layout.nav.saved-projects": {
|
||||
"message": "Opgeslagen projecten"
|
||||
},
|
||||
"layout.nav.search": {
|
||||
"message": "Zoeken"
|
||||
},
|
||||
@@ -1739,15 +1760,42 @@
|
||||
"profile.button.manage-projects": {
|
||||
"message": "Beheer projecten"
|
||||
},
|
||||
"profile.details.label.email": {
|
||||
"message": "Email"
|
||||
},
|
||||
"profile.details.label.email-verified": {
|
||||
"message": "Email geverifieerd"
|
||||
},
|
||||
"profile.details.label.has-password": {
|
||||
"message": "Heeft wachtwoord"
|
||||
},
|
||||
"profile.details.label.has-totp": {
|
||||
"message": "Heeft TOTP"
|
||||
},
|
||||
"profile.details.label.payment-methods": {
|
||||
"message": "Betaalmethodes"
|
||||
},
|
||||
"profile.details.tooltip.email-not-verified": {
|
||||
"message": "Email niet geverifieerd"
|
||||
},
|
||||
"profile.details.tooltip.email-verified": {
|
||||
"message": "Email geverifieerd"
|
||||
},
|
||||
"profile.error.not-found": {
|
||||
"message": "Gebruiker niet gevonden"
|
||||
},
|
||||
"profile.label.badges": {
|
||||
"message": "Badges"
|
||||
},
|
||||
"profile.label.collection": {
|
||||
"message": "Collectie"
|
||||
},
|
||||
"profile.label.details": {
|
||||
"message": "Details"
|
||||
},
|
||||
"profile.label.no": {
|
||||
"message": "Nee"
|
||||
},
|
||||
"profile.label.no-collections": {
|
||||
"message": "Deze gebruiker heeft geen collecties!"
|
||||
},
|
||||
@@ -2270,6 +2318,15 @@
|
||||
"scopes.reportWrite.description": {
|
||||
"message": "Meldingen bewerken"
|
||||
},
|
||||
"scopes.sessionAccess.label": {
|
||||
"message": "Toegang tot sessies"
|
||||
},
|
||||
"scopes.sessionDelete.description": {
|
||||
"message": "Verwijder sessies"
|
||||
},
|
||||
"scopes.sessionDelete.label": {
|
||||
"message": "Verwijder sessies"
|
||||
},
|
||||
"scopes.sessionRead.description": {
|
||||
"message": "Lees actieve sessies"
|
||||
},
|
||||
@@ -2381,6 +2438,15 @@
|
||||
"servers.plan.large.description": {
|
||||
"message": "Ideaal voor 15-25 spelers, modpacks of zwaar modificeren"
|
||||
},
|
||||
"servers.plan.large.name": {
|
||||
"message": "Groot"
|
||||
},
|
||||
"servers.plan.medium.name": {
|
||||
"message": "Medium"
|
||||
},
|
||||
"servers.plan.small.name": {
|
||||
"message": "Klein"
|
||||
},
|
||||
"settings.billing.payment_method.none": {
|
||||
"message": "Je hebt geen betaalmethodes toegevoegd."
|
||||
},
|
||||
|
||||
@@ -987,10 +987,10 @@
|
||||
"message": "Konto nie jest podpięte"
|
||||
},
|
||||
"dashboard.withdraw.error.email-verification.text": {
|
||||
"message": "Musisz zweryfikować swój adres E-mail przed wpłatą pieniędzy"
|
||||
"message": "Musisz zweryfikować swój adres e-mail przed wpłatą pieniędzy."
|
||||
},
|
||||
"dashboard.withdraw.error.email-verification.title": {
|
||||
"message": "Wymagana weryfikacja E-maila"
|
||||
"message": "Wymagana weryfikacja e-maila"
|
||||
},
|
||||
"dashboard.withdraw.error.generic.text": {
|
||||
"message": "Nie udało się przesłać prośby o wypłatę, sprawdź podane dane lub skontaktuj się z obsługą."
|
||||
@@ -1134,7 +1134,7 @@
|
||||
"message": "Fotorealistyczny obraz żabiego labiryntu"
|
||||
},
|
||||
"frog.froggedPeople": {
|
||||
"message": "{count, plural, one {{count} osoba też została zżabowana!} other {{count} ludzi też zostało zżabowanych!}}"
|
||||
"message": "{count, plural, one {{count} osoba też została zżabowana} other {{count} ludzi też zostało zżabowanych}}!"
|
||||
},
|
||||
"frog.sinceOpened": {
|
||||
"message": "Ta strona została otwarta {ago}"
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Błąd podczas generowania stanu z API przy kompilacji."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Jeśli chciałeś wejść na oficjalną stronę Modrinth, odwiedź <link>https://modrinth.com</link>. To wydanie poglądowe jest używane przez administrację Modrinth do testów. Zostało utworzone z <branch-link>{owner}/{branch}</branch-link> @ {commit}."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "To jest wydanie poglądowe strony Modrinth."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "Środowisko testowe jest całkowicie oddzielone od produkcyjnej bazy danych Modrinth. Służy ono do testowania i debugowania i może działać w wersjach rozwojowych lub interfejsu użytkownika Modrinth, które są nowsze niż instancja produkcyjna."
|
||||
},
|
||||
@@ -1398,7 +1404,7 @@
|
||||
"message": "Polityka"
|
||||
},
|
||||
"layout.footer.legal-disclaimer": {
|
||||
"message": "NIE JEST TO OFICJALNA USŁUGA MINECRAFT. NIE JEST ZATWIERDZONA ANI POWIĄZANA Z MOJANG LUB MICROSOFT."
|
||||
"message": "TO NIE JEST OFICJALNA USŁUGA MINECRAFT. NIE JEST ZATWIERDZONA ANI POWIĄZANA Z MOJANG LUB MICROSOFT."
|
||||
},
|
||||
"layout.footer.legal.copyright-policy": {
|
||||
"message": "Polityka dotycząca praw autorskich i ustawa DMCA"
|
||||
@@ -2478,7 +2484,7 @@
|
||||
"message": "Tworzenie zgłoszeń"
|
||||
},
|
||||
"scopes.reportCreate.label": {
|
||||
"message": "Tworzenie raportów"
|
||||
"message": "Tworzenie zgłoszeń"
|
||||
},
|
||||
"scopes.reportDelete.description": {
|
||||
"message": "Usuń raporty"
|
||||
|
||||
@@ -1337,6 +1337,12 @@
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Erro ao gerar o estado da API durante a compilação."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Se você pretendia acessar o site oficial do Modrinth, visite <link>https://modrinth.com</link>. Essa versão de pré-lançamento é usada pela equipe da Modrinth para fins de teste. Ela foi construída com base em <branch-link>{owner}/{branch}</branch-link> @ {commit}."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Esta é uma versão de pré-lançamento do site Modrinth."
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "O ambiente de desenvolvimento é completamente separado do banco de dados padrão do Modrinth. Ele é usado para testes e depurações, e pode estar rodando versões mais novas, em desenvolvimento, do backend ou frontend do Modrinth."
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user