Compare commits

..
Author SHA1 Message Date
François-X. T. 7d2e1a8be9 Add feature flag to force Archon requests to be traced 2026-03-25 20:14:50 -04:00
711 changed files with 23346 additions and 57512 deletions
@@ -22,5 +22,4 @@ Refer to the standards: @standards/frontend/CROSS_PLATFORM_PAGES.md and @standar
- Move the page component into `packages/ui/src/layouts/wrapped/` matching the route structure.
- Replace any platform-specific imports with shared utilities.
- Import and render the wrapped page from both frontends as a simple component.
- If the layout uses TanStack Query for initial route paint with `ReadyTransition` / `useReadyState`, each platform route shell must call `ensureQueryData` for those queries with matching keys and fetchers — see **Platform route shells: prefetch with `ensureQueryData`** in `standards/frontend/CROSS_PLATFORM_PAGES.md`.
6. **Verify** the page renders correctly by checking for missing imports and that all DI contracts are satisfied.
-36
View File
@@ -1,36 +0,0 @@
---
name: review-changelog
description: Review the latest changelog entry in packages/blog/changelog.ts against the project's changelog style guide and flag bullets that need rewriting. Use when checking a freshly added changelog entry before opening a PR, or when the user asks to review/lint the latest changelog.
argument-hint: [product?]
---
Refer to the standard: @standards/maintaining/CHANGELOG.md
## Steps
1. **Locate the latest entry:**
- Open `packages/blog/changelog.ts`.
- The latest entries are at the top of the `VERSIONS` array.
- If `$ARGUMENTS` specifies a product (`web`, `hosting`, `app`), review the most recent entry for that product. Otherwise, review the most recent entry overall, plus any sibling entries sharing the same `date` (coordinated releases ship together).
2. **Read the standard above** in full before reviewing. The bullet rules, section/verb agreement, and "Don't" list are the source of truth.
3. **Check the entry shell:**
- `date` is a valid ISO 8601 timestamp.
- `product` is one of `web`, `hosting`, `app`.
- `version` is present for `app` entries and omitted for `web`/`hosting`.
- Section headings use `## Added`, `## Changed`, `## Fixed`, `## Security` (or a featured-release linked heading). Flag legacy `## Improvements`.
4. **Review each bullet** against the standard. For each bullet, check:
- Voice/tense matches the section heading.
- Opening verb agrees with its section.
- Describes observable behavior, not implementation.
- Specific enough to identify the surface (names the tab/page/modal).
- One sentence, ends with a period, sentence case.
- Uses branded names (Modrinth App, Modrinth Hosting) correctly.
- No filler ("issue with", "issue where", "various", "some"), no vague intensifiers, no apologies, no PR/commit references (unless crediting a third-party contributor with a linked GitHub profile).
- Not a duplicate sub-fix of a bigger change already listed.
5. **Report findings** as a short list grouped by entry. For each problem bullet, show the original line and a suggested rewrite. If the entry is clean, say so explicitly. Do not edit the file unless the user asks - this skill is a review pass, not a rewrite pass.
6. **If the user then asks to apply fixes**, edit `packages/blog/changelog.ts` directly using the suggested rewrites. Preserve tab indentation and template literal formatting.
+1 -1
View File
@@ -1,7 +1,7 @@
name: Changelog Comment
on:
pull_request_target:
pull_request:
types: [opened, reopened]
workflow_dispatch:
inputs:
-3
View File
@@ -27,15 +27,12 @@ on:
options:
- prod
- staging
- prod-with-staging-archon
default: prod
required: false
jobs:
build:
name: Build
env:
VITE_STRIPE_PUBLISHABLE_KEY: pk_live_51JbFxJJygY5LJFfKLVVldb10HlLt24p421OWRsTOWc5sXYFOnFUXWieSc6HD3PHo25ktx8db1WcHr36XGFvZFVUz00V9ixrCs5
strategy:
fail-fast: false
matrix:
+2 -96
View File
@@ -9,7 +9,6 @@ jobs:
name: Release Modrinth App
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v')
runs-on: ubuntu-latest
@@ -24,25 +23,6 @@ jobs:
- name: 📥 Check out code
uses: actions/checkout@v4
- name: 🔒 Verify ref is a tag
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
tag_sha=$(gh api "repos/${{ github.repository }}/git/refs/tags/${VERSION_TAG}" --jq '.object.sha' 2>/dev/null || true)
if [ -z "$tag_sha" ]; then
echo "::error::${VERSION_TAG} is not a git tag in this repo"
exit 1
fi
if [ "$tag_sha" != "$HEAD_SHA" ]; then
commit_sha=$(gh api "repos/${{ github.repository }}/git/tags/${tag_sha}" --jq '.object.sha' 2>/dev/null || echo "$tag_sha")
if [ "$commit_sha" != "$HEAD_SHA" ]; then
echo "::error::Tag ${VERSION_TAG} (${commit_sha}) does not match build head_sha (${HEAD_SHA})"
exit 1
fi
fi
echo "Verified ${VERSION_TAG} is a tag pointing at ${HEAD_SHA}"
- name: 📥 Download Modrinth App artifacts
uses: dawidd6/action-download-artifact@v11
with:
@@ -52,86 +32,12 @@ jobs:
branch: ${{ env.VERSION_TAG }}
use_unzip: true
- name: 📝 Extract app changelog
env:
VERSION: ${{ env.VERSION_TAG }}
run: |
node <<'EOF'
const fs = require('fs');
const version = process.env.VERSION.replace(/^v/, '');
const src = fs.readFileSync('packages/blog/changelog.ts', 'utf8');
// Parse every entry in the VERSIONS array, preserving their order
// (which is reverse chronological).
const entryRe = /\{\s*date:\s*`([^`]+)`,\s*product:\s*'(\w+)',(?:\s*version:\s*[`']([^`']+)[`'],)?\s*body:\s*`([\s\S]*?)`,\s*\}/g;
const entries = [];
let match;
while ((match = entryRe.exec(src)) !== null) {
entries.push({
date: match[1],
product: match[2],
version: match[3],
body: match[4],
});
}
const currentIdx = entries.findIndex(
(e) => e.product === 'app' && e.version === version,
);
if (currentIdx === -1) {
console.error(`No app changelog entry found for version ${version}`);
process.exit(1);
}
// Find the surrounding app entries so we can scope hosting changes to
// exactly what shipped between the previous app release and this one.
// Entries are in reverse chronological order, so newer entries have
// smaller indices than older entries.
let newerAppIdx = -1;
for (let i = currentIdx - 1; i >= 0; i--) {
if (entries[i].product === 'app') {
newerAppIdx = i;
break;
}
}
let previousAppIdx = entries.length;
for (let i = currentIdx + 1; i < entries.length; i++) {
if (entries[i].product === 'app') {
previousAppIdx = i;
break;
}
}
const hostingEntries = [];
for (let i = newerAppIdx + 1; i < previousAppIdx; i++) {
if (entries[i].product === 'hosting') {
hostingEntries.push(entries[i]);
}
}
let output = entries[currentIdx].body;
if (hostingEntries.length > 0) {
// Demote any top-level section headings inside hosting bodies so
// they nest cleanly under the "Modrinth Hosting (included)" header.
const demoteHeadings = (body) =>
body.replace(/^(#{1,5})\s/gm, (_, hashes) => `${hashes}# `);
const hostingBody = hostingEntries
.map((e) => demoteHeadings(e.body))
.join('\n\n');
output += `\n\n---\n\n## Modrinth Hosting (included)\n\n${hostingBody}`;
}
fs.writeFileSync('release-notes.md', output);
console.log(`Extracted changelog for app ${version}:`);
console.log(output);
EOF
- name: 🛠️ Generate version manifest
run: |
# Reference: https://tauri.app/plugin/updater/#server-support
jq -nc \
--arg versionTag "${VERSION_TAG#v}" \
--rawfile releaseNotes release-notes.md \
--arg releaseNotes "See the full changelog at https://modrinth.com/news/changelog" \
--rawfile macOsAarch64UpdateArtifactSignature "${MACOS_UNIVERSAL_BUNDLE_ARTIFACT_NAME}/universal-apple-darwin/release/bundle/macos/Modrinth App.app.tar.gz.sig" \
--rawfile macOsX64UpdateArtifactSignature "${MACOS_UNIVERSAL_BUNDLE_ARTIFACT_NAME}/universal-apple-darwin/release/bundle/macos/Modrinth App.app.tar.gz.sig" \
--rawfile linuxX64UpdateArtifactSignature "${LINUX_X64_BUNDLE_ARTIFACT_NAME}/release/bundle/appimage/Modrinth App_${VERSION_TAG#v}_amd64.AppImage.tar.gz.sig" \
@@ -214,7 +120,7 @@ jobs:
gh release create "$VERSION_TAG" \
--title "Modrinth App ${VERSION}" \
--notes-file release-notes.md \
--notes "See the full changelog at https://modrinth.com/news/changelog" \
"${WINDOWS_X64_BUNDLE_ARTIFACT_NAME}/release/bundle/nsis/Modrinth App_${VERSION}_x64-setup.exe" \
"${MACOS_UNIVERSAL_BUNDLE_ARTIFACT_NAME}/universal-apple-darwin/release/bundle/dmg/Modrinth App_${VERSION}_universal.dmg" \
"${LINUX_X64_BUNDLE_ARTIFACT_NAME}/release/bundle/appimage/Modrinth App_${VERSION}_amd64.AppImage" \
-3
View File
@@ -78,6 +78,3 @@ storybook-static
# frontend robots.txt
apps/frontend/src/public/robots.txt
# Oh My Code
.omc/
+1 -3
View File
@@ -29,7 +29,5 @@
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
},
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore"
}
}
+1
View File
@@ -0,0 +1 @@
See [CLAUDE.md](./CLAUDE.md) for all project instructions and guidelines.
-1
View File
@@ -86,7 +86,6 @@ Each project may have its own `CLAUDE.md` with detailed instructions:
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
- For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc.
- Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client`
- When provided problems, do not say "I didn't introduce these problems" (shifting the blame/effort) - just fix them.
## Edit Tool - Whitespace Handling (CLAUDE ONLY)
Generated
+65 -7
View File
@@ -4973,7 +4973,7 @@ dependencies = [
"urlencoding",
"utoipa",
"utoipa-actix-web",
"utoipa-scalar",
"utoipa-swagger-ui",
"uuid 1.18.1",
"validator",
"webp",
@@ -7950,6 +7950,40 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-embed"
version = "8.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb44e1917075637ee8c7bcb865cf8830e3a92b5b1189e44e3a0ab5a0d5be314b"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "382499b49db77a7c19abd2a574f85ada7e9dbe125d5d1160fa5cad7c4cf71fc9"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.106",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21fcbee55c2458836bcdbfffb6ec9ba74bbc23ca7aa6816015a3dd2c4d8fc185"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -10155,7 +10189,6 @@ dependencies = [
"either",
"encoding_rs",
"enumset",
"eyre",
"flate2",
"fs4",
"futures",
@@ -10208,7 +10241,6 @@ dependencies = [
name = "theseus_gui"
version = "1.0.0-local"
dependencies = [
"async_zip",
"chrono",
"daedalus",
"dashmap",
@@ -10226,7 +10258,6 @@ dependencies = [
"tauri-build",
"tauri-plugin-deep-link",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-http",
"tauri-plugin-opener",
"tauri-plugin-os",
@@ -11166,17 +11197,30 @@ dependencies = [
]
[[package]]
name = "utoipa-scalar"
version = "0.3.0"
name = "utoipa-swagger-ui"
version = "9.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
dependencies = [
"actix-web",
"base64 0.22.1",
"mime_guess",
"regex",
"rust-embed",
"serde",
"serde_json",
"url",
"utoipa",
"utoipa-swagger-ui-vendored",
"zip 3.0.0",
]
[[package]]
name = "utoipa-swagger-ui-vendored"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d"
[[package]]
name = "uuid"
version = "0.8.2"
@@ -12561,6 +12605,20 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "zip"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
dependencies = [
"arbitrary",
"crc32fast",
"flate2",
"indexmap 2.11.4",
"memchr",
"zopfli",
]
[[package]]
name = "zip"
version = "4.6.1"
+1 -2
View File
@@ -179,7 +179,6 @@ tauri = "2.8.5"
tauri-build = "2.4.1"
tauri-plugin-deep-link = "2.4.3"
tauri-plugin-dialog = "2.4.0"
tauri-plugin-fs = "2.4.5"
tauri-plugin-http = "2.5.7"
tauri-plugin-opener = "2.5.0"
tauri-plugin-os = "2.3.1"
@@ -208,7 +207,7 @@ url = "2.5.7"
urlencoding = "2.1.3"
utoipa = { version = "5.4.0", features = ["actix_extras", "chrono", "decimal"] }
utoipa-actix-web = { version = "0.1.2" }
utoipa-scalar = { version = "0.3.0", default-features = false }
utoipa-swagger-ui = { version = "9.0.2", features = ["actix-web", "vendored"] }
uuid = "1.18.1"
validator = "0.20.0"
webp = { version = "0.3.1", default-features = false }
+1 -3
View File
@@ -22,7 +22,6 @@
"@tanstack/vue-query": "^5.90.7",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.2.1",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-http": "~2.5.7",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-os": "^2.2.1",
@@ -35,7 +34,6 @@
"fuse.js": "^6.6.2",
"intl-messageformat": "^10.7.7",
"ofetch": "^1.3.4",
"overlayscrollbars": "^2.15.1",
"pinia": "^3.0.0",
"posthog-js": "^1.158.2",
"three": "^0.172.0",
@@ -60,7 +58,7 @@
"sass": "^1.74.1",
"tailwindcss": "^3.4.4",
"typescript": "^5.5.4",
"vite": "^8.0.0",
"vite": "^6.0.0",
"vue-component-type-helpers": "^3.1.8",
"vue-tsc": "^2.1.6"
},
+123 -186
View File
@@ -1,12 +1,5 @@
<script setup>
import {
AuthFeature,
NodeAuthFeature,
nodeAuthState,
PanelVersionFeature,
TauriModrinthClient,
VerboseLoggingFeature,
} from '@modrinth/api-client'
import { AuthFeature, PanelVersionFeature, TauriModrinthClient } from '@modrinth/api-client'
import {
ArrowBigUpDashIcon,
ChangeSkinIcon,
@@ -18,12 +11,15 @@ import {
LibraryIcon,
LogInIcon,
LogOutIcon,
MaximizeIcon,
MinimizeIcon,
NewspaperIcon,
NotepadTextIcon,
PlusIcon,
RefreshCwIcon,
RestoreIcon,
RightArrowIcon,
ServerStackIcon,
ServerIcon,
SettingsIcon,
UserIcon,
WorldIcon,
@@ -32,13 +28,13 @@ import {
import {
Admonition,
Avatar,
Button,
ButtonStyled,
commonMessages,
ContentInstallModal,
CreationFlowModal,
defineMessages,
I18nDebugPanel,
LoadingBar,
NewsArticleCard,
NotificationPanel,
OverflowMenu,
@@ -53,11 +49,10 @@ import {
useVIntl,
} from '@modrinth/ui'
import { formatBytes, renderString } from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { useQuery } from '@tanstack/vue-query'
import { getVersion } from '@tauri-apps/api/app'
import { invoke } from '@tauri-apps/api/core'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
import { openUrl } from '@tauri-apps/plugin-opener'
import { type } from '@tauri-apps/plugin-os'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
@@ -66,6 +61,7 @@ import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
import AccountsCard from '@/components/ui/AccountsCard.vue'
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import ErrorModal from '@/components/ui/ErrorModal.vue'
@@ -83,9 +79,7 @@ import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
import WindowControls from '@/components/ui/WindowControls.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { config } from '@/config'
import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js'
import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
@@ -112,10 +106,8 @@ import {
} from '@/providers/download-progress.ts'
import { createServerInstall, provideServerInstall } from '@/providers/server-install'
import { setupProviders } from '@/providers/setup'
import { setupAuthProvider } from '@/providers/setup/auth'
import { setupLoadingStateProvider } from '@/providers/setup/loading-state'
import { useError } from '@/store/error.js'
import { useTheming } from '@/store/state'
import { useLoading, useTheming } from '@/store/state'
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
import { get_available_capes, get_available_skins } from './helpers/skins'
@@ -134,34 +126,17 @@ const { addPopupNotification } = popupNotificationManager
const tauriApiClient = new TauriModrinthClient({
userAgent: `modrinth/theseus/${getVersion()} (support@modrinth.com)`,
labrinthBaseUrl: config.labrinthBaseUrl,
archonBaseUrl: config.archonBaseUrl,
features: [
new NodeAuthFeature({
getAuth: () => nodeAuthState.getAuth?.() ?? null,
refreshAuth: async () => {
if (nodeAuthState.refreshAuth) {
await nodeAuthState.refreshAuth()
}
},
}),
new AuthFeature({
token: async () => (await getCreds())?.session,
token: async () => (await getCreds()).session,
}),
new PanelVersionFeature(),
new VerboseLoggingFeature(),
],
})
provideModrinthClient(tauriApiClient)
providePageContext({
hierarchicalSidebarAvailable: ref(true),
showAds: ref(false),
featureFlags: {
serverRamAsBytesAlwaysOn: computed(() =>
themeStore.getFeatureFlag('server_ram_as_bytes_always_on'),
),
},
openExternalUrl: (url) => openUrl(url),
})
provideModalBehavior({
noblur: computed(() => !themeStore.advancedRendering),
@@ -419,112 +394,17 @@ const handleClose = async () => {
}
const router = useRouter()
const route = useRoute()
const loading = setupLoadingStateProvider()
loading.setEnabled(false)
let initialLoadToken = loading.begin()
let routerToken = null
let suspenseToken = null
let suspensePending = false
const sidebarOverlayScrollbarsOptions = Object.freeze({
overflow: {
x: 'hidden',
y: 'scroll',
},
})
router.beforeEach(() => {
suspensePending = false
if (routerToken) loading.end(routerToken)
routerToken = loading.begin()
})
router.afterEach((to, from, failure) => {
trackEvent('PageView', {
path: to.path,
fromPath: from.path,
failed: failure,
})
setTimeout(() => {
if (!suspensePending && stateInitialized.value) {
if (initialLoadToken) {
loading.end(initialLoadToken)
initialLoadToken = null
}
if (routerToken) {
loading.end(routerToken)
routerToken = null
}
}
}, 100)
})
const route = useRoute()
function onSuspensePending() {
suspensePending = true
if (suspenseToken) loading.end(suspenseToken)
suspenseToken = loading.begin()
}
function onSuspenseResolve() {
if (suspenseToken) {
loading.end(suspenseToken)
suspenseToken = null
}
if (routerToken) {
loading.end(routerToken)
routerToken = null
}
}
const queryClient = useQueryClient()
watch(stateInitialized, (ready) => {
if (ready) {
if (initialLoadToken) {
loading.end(initialLoadToken)
initialLoadToken = null
}
if (routerToken) {
loading.end(routerToken)
routerToken = null
}
queryClient.prefetchQuery({
queryKey: ['servers'],
queryFn: async () => {
const response = await tauriApiClient.archon.servers_v0.list({ limit: 100 })
const hasMedalServers = response.servers.some((s) => s.is_medal)
if (hasMedalServers) {
const subscriptions = await tauriApiClient.labrinth.billing_internal.getSubscriptions()
for (const server of response.servers) {
if (server.is_medal) {
const sub = subscriptions.find((s) => s.metadata?.id === server.server_id)
if (sub) {
server.medal_expires = new Date(
new Date(sub.created).getTime() + 5 * 86400000,
).toISOString()
}
}
}
}
return response
},
staleTime: 30_000,
})
queryClient.prefetchQuery({
queryKey: ['billing', 'subscriptions'],
queryFn: () => tauriApiClient.labrinth.billing_internal.getSubscriptions(),
staleTime: 30_000,
})
queryClient.prefetchQuery({
queryKey: ['billing', 'payments'],
queryFn: () => tauriApiClient.labrinth.billing_internal.getPayments(),
staleTime: 30_000,
})
}
})
const loading = useLoading()
loading.setEnabled(false)
const error = useError()
const errorModal = ref()
@@ -574,31 +454,9 @@ const credentials = ref()
const modrinthLoginFlowWaitModal = ref()
setupAuthProvider(credentials, async (_redirectPath) => {
await signIn()
})
async function validateSession(sessionToken) {
try {
const response = await tauriFetch(`${config.labrinthBaseUrl}/v2/user`, {
method: 'GET',
headers: { Authorization: sessionToken },
})
if (response.status === 401) return false
return true
} catch {
return true
}
}
async function fetchCredentials() {
const creds = await getCreds().catch(handleError)
if (creds && creds.user_id) {
if (creds.session && !(await validateSession(creds.session))) {
await logout().catch(handleError)
credentials.value = null
return
}
creds.user = await get_user(creds.user_id, 'bypass').catch(handleError)
}
credentials.value = creds ?? null
@@ -1072,7 +930,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</script>
<template>
<WindowControls />
<SplashScreen v-if="!stateFailed" ref="splashScreen" data-tauri-drag-region />
<div id="teleports"></div>
<div
@@ -1120,6 +977,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<NavButton v-if="themeStore.featureFlags.worlds_tab" v-tooltip.right="'Worlds'" to="/worlds">
<WorldIcon />
</NavButton>
<NavButton
v-if="themeStore.featureFlags.servers_in_app"
v-tooltip.right="'Servers'"
to="/hosting/manage"
>
<ServerIcon />
</NavButton>
<NavButton
v-tooltip.right="'Discover content'"
to="/browse/modpack"
@@ -1134,7 +998,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<NavButton
v-tooltip.right="'Library'"
to="/library"
:is-primary="(r) => r.path === '/library' || r.path === '/library'"
:is-subpage="
() =>
route.path.startsWith('/instance') ||
@@ -1144,14 +1007,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
<LibraryIcon />
</NavButton>
<NavButton
v-tooltip.right="'Modrinth Hosting'"
to="/hosting/manage"
:is-primary="(r) => r.path === '/hosting/manage' || r.path === '/hosting/manage/'"
:is-subpage="(r) => r.path.startsWith('/hosting/manage/') && r.path !== '/hosting/manage/'"
>
<ServerStackIcon />
</NavButton>
<div class="h-px w-6 mx-auto my-2 bg-surface-5"></div>
<suspense>
<QuickInstanceSwitcher />
@@ -1270,6 +1125,22 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<RunningAppBar />
</Suspense>
</div>
<section v-if="!nativeDecorations" class="window-controls" data-tauri-drag-region-exclude>
<Button class="titlebar-button" icon-only @click="() => getCurrentWindow().minimize()">
<MinimizeIcon />
</Button>
<Button
class="titlebar-button"
icon-only
@click="() => getCurrentWindow().toggleMaximize()"
>
<RestoreIcon v-if="isMaximized" />
<MaximizeIcon v-else />
</Button>
<Button class="titlebar-button close" icon-only @click="handleClose">
<XIcon />
</Button>
</section>
</section>
</div>
</div>
@@ -1305,14 +1176,14 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</div>
</transition>
<div
class="loading-indicator-container h-8 fixed z-50 pointer-events-none"
class="loading-indicator-container h-8 fixed z-50"
:style="{
top: 'calc(var(--top-bar-height))',
left: 'calc(var(--left-bar-width))',
width: 'calc(100% - var(--left-bar-width) - var(--right-bar-width))',
}"
>
<LoadingBar position="absolute" />
<ModrinthLoadingIndicator />
</div>
<div
v-if="themeStore.featureFlags.page_path"
@@ -1348,36 +1219,40 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</Admonition>
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
<component :is="Component"></component>
</Suspense>
</template>
</RouterView>
</div>
<div
class="app-sidebar mt-px shrink-0 flex flex-col border-0 border-l-[1px] border-[--brand-gradient-border] border-solid"
class="app-sidebar mt-px shrink-0 flex flex-col border-0 border-l-[1px] border-[--brand-gradient-border] border-solid overflow-auto"
:class="{ 'has-plus': hasPlus }"
>
<div
v-overlay-scrollbars="sidebarOverlayScrollbarsOptions"
class="app-sidebar-scrollable flex-grow shrink relative"
class="app-sidebar-scrollable flex-grow shrink overflow-y-auto relative"
:class="{ 'pb-12': !hasPlus }"
data-overlayscrollbars-initialize
>
<div id="sidebar-teleport-target" class="sidebar-teleport-content"></div>
<div class="sidebar-default-content" :class="{ 'sidebar-enabled': sidebarVisible }">
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<div
class="p-4 pr-1 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"
>
<h3 class="text-base text-primary font-medium m-0">Playing as</h3>
<suspense>
<AccountsCard ref="accounts" mode="small" />
</suspense>
</div>
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<div class="py-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<suspense>
<FriendsList :credentials="credentials" :sign-in="() => signIn()" />
<FriendsList
:credentials="credentials"
:sign-in="() => signIn()"
:refresh-credentials="fetchCredentials"
/>
</suspense>
</div>
<div v-if="news && news.length > 0" class="p-4 flex flex-col items-center">
<div v-if="news && news.length > 0" class="p-4 pr-1 flex flex-col items-center">
<h3 class="text-base mb-4 text-primary font-medium m-0 text-left w-full">News</h3>
<div class="space-y-4 flex flex-col items-center w-full">
<NewsArticleCard
@@ -1407,8 +1282,8 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</div>
</div>
<I18nDebugPanel />
<NotificationPanel :has-sidebar="sidebarVisible" />
<PopupNotificationPanel :has-sidebar="sidebarVisible" />
<NotificationPanel has-sidebar />
<PopupNotificationPanel has-sidebar />
<ErrorModal ref="errorModal" />
<MinecraftAuthErrorModal ref="minecraftAuthErrorModal" />
<ContentInstallModal
@@ -1444,6 +1319,72 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</template>
<style lang="scss" scoped>
.window-controls {
z-index: 20;
display: none;
flex-direction: row;
align-items: center;
.titlebar-button {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all ease-in-out 0.1s;
background-color: transparent;
color: var(--color-base);
height: 100%;
width: 3rem;
position: relative;
box-shadow: none;
&:last-child {
padding-right: 0.75rem;
width: 3.75rem;
}
svg {
width: 1.25rem;
height: 1.25rem;
}
&::before {
content: '';
border-radius: 999999px;
width: 3rem;
height: 3rem;
aspect-ratio: 1 / 1;
margin-block: auto;
position: absolute;
background-color: transparent;
scale: 0.9;
transition: all ease-in-out 0.2s;
z-index: -1;
}
&.close {
&:hover,
&:active {
color: var(--color-accent-contrast);
&::before {
background-color: var(--color-red);
}
}
}
&:hover,
&:active {
color: var(--color-contrast);
&::before {
background-color: var(--color-button-bg);
scale: 1;
}
}
}
}
.app-grid-layout,
.app-contents {
--top-bar-height: 3rem;
@@ -1468,7 +1409,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
.app-grid-statusbar {
grid-area: status;
padding-right: var(--window-controls-width, 0px);
}
[data-tauri-drag-region-exclude] {
@@ -1660,13 +1600,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
}
</style>
<style>
.os-theme-dark,
.os-theme-light {
--os-handle-bg: var(--color-scrollbar) !important;
--os-handle-bg-hover: var(--color-scrollbar) !important;
--os-handle-bg-active: var(--color-scrollbar) !important;
}
.mac {
.app-grid-statusbar {
padding-left: 5rem;
@@ -1678,6 +1611,10 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
height: 2.5rem !important;
}
.window-controls {
display: flex !important;
}
.info-card {
right: 22rem;
}
@@ -45,14 +45,6 @@
color-scheme: dark;
--view-width: calc(100% - 5rem);
--expanded-view-width: calc(100% - 13rem);
--medal-promotion-bg: #000;
--medal-promotion-bg-orange: rgba(208, 246, 255, 0.25);
--medal-promotion-text-orange: #42abff;
--medal-promotion-bg-gradient: linear-gradient(
90deg,
rgba(66, 170, 255, 0.15),
rgba(0, 0, 0, 0) 100%
);
}
body {
@@ -85,10 +77,12 @@ body {
}
a {
color: inherit;
color: var(--color-link);
text-decoration: none;
-webkit-font-smoothing: antialiased;
will-change: filter;
&:hover {
text-decoration: none;
}
}
.badge {
@@ -180,63 +174,4 @@ img {
}
}
button,
input[type='button'] {
cursor: pointer;
border: none;
outline: 2px solid transparent;
}
@import '@modrinth/assets/omorphia.scss';
input {
border-radius: var(--size-rounded-sm);
box-sizing: border-box;
border: 2px solid transparent;
// safari iOS rounds inputs by default
// set the appearance to none to prevent this
appearance: none !important;
}
pre {
font-weight: var(--font-weight-regular);
}
input,
textarea {
background: var(--color-button-bg);
color: var(--color-text);
padding: 0.5rem 1rem;
font-weight: var(--font-weight-medium);
border: none;
outline: 2px solid transparent;
box-shadow:
var(--shadow-inset-sm),
0 0 0 0 transparent;
transition: box-shadow 0.1s ease-in-out;
min-height: 36px;
&:focus,
&:focus-visible {
box-shadow:
inset 0 0 0 transparent,
0 0 0 0.25rem var(--color-brand-shadow);
color: var(--color-button-text-active);
}
&:disabled,
&[disabled='true'] {
opacity: 0.6;
pointer-events: none;
cursor: not-allowed;
}
&:focus::placeholder {
opacity: 0.8;
}
&::placeholder {
color: var(--color-button-text);
opacity: 0.6;
}
}
@@ -10,7 +10,6 @@ import {
TrashIcon,
} from '@modrinth/assets'
import {
Accordion,
DropdownSelect,
formatLoader,
injectNotificationManager,
@@ -134,33 +133,12 @@ const state = useStorage(
{
group: 'Group',
sortBy: 'Name',
collapsedGroups: [],
},
localStorage,
{ mergeDefaults: true },
)
const search = ref('')
const collapsedSectionKeys = computed(() => new Set(state.value.collapsedGroups ?? []))
const getSectionKey = (sectionName) => `${state.value.group}:${sectionName}`
const isSectionCollapsed = (sectionName) => {
return collapsedSectionKeys.value.has(getSectionKey(sectionName))
}
const setSectionCollapsed = (sectionName, collapsed) => {
const sectionKey = getSectionKey(sectionName)
const collapsedSections = new Set(state.value.collapsedGroups ?? [])
if (collapsed) {
collapsedSections.add(sectionKey)
} else {
collapsedSections.delete(sectionKey)
}
state.value.collapsedGroups = [...collapsedSections]
}
const filteredResults = computed(() => {
const { group = 'Group', sortBy = 'Name' } = state.value
@@ -302,21 +280,18 @@ const filteredResults = computed(() => {
<span class="font-semibold text-secondary">{{ selected }}</span>
</DropdownSelect>
</div>
<Accordion
<div
v-for="instanceSection in Array.from(filteredResults, ([key, value]) => ({
key,
value,
}))"
:key="instanceSection.key"
:divider="instanceSection.key !== 'None'"
:open-by-default="!isSectionCollapsed(instanceSection.key)"
class="row"
@on-open="setSectionCollapsed(instanceSection.key, false)"
@on-close="setSectionCollapsed(instanceSection.key, true)"
>
<template v-if="instanceSection.key !== 'None'" #title>
<span class="text-base">{{ instanceSection.key }}</span>
</template>
<div v-if="instanceSection.key !== 'None'" class="divider">
<p>{{ instanceSection.key }}</p>
<hr aria-hidden="true" />
</div>
<section class="instances">
<Instance
v-for="instance in instanceSection.value"
@@ -326,7 +301,7 @@ const filteredResults = computed(() => {
@contextmenu.prevent.stop="(event) => handleRightClick(event, instance.path)"
/>
</section>
</Accordion>
</div>
<ConfirmDeleteInstanceModal ref="confirmModal" @delete="deleteProfile" />
<ContextMenu ref="instanceOptions" @option-clicked="handleOptionsClick">
<template #play> <PlayIcon /> Play </template>
@@ -341,7 +316,73 @@ const filteredResults = computed(() => {
</template>
<style lang="scss" scoped>
.row {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
.divider {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
gap: 1rem;
margin-bottom: 1rem;
p {
margin: 0;
font-size: 1rem;
white-space: nowrap;
color: var(--color-contrast);
}
hr {
background-color: var(--color-gray);
height: 1px;
width: 100%;
border: none;
}
}
}
.header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
align-items: inherit;
margin: 1rem 1rem 0 !important;
padding: 1rem;
width: calc(100% - 2rem);
.iconified-input {
flex-grow: 1;
input {
min-width: 100%;
}
}
.sort-dropdown {
width: 10rem;
}
.filter-dropdown {
width: 15rem;
}
.group-dropdown {
width: 10rem;
}
.labeled_button {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
}
.instances {
@@ -0,0 +1,142 @@
<script setup>
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useLoading } from '@/store/state.js'
const props = defineProps({
throttle: {
type: Number,
default: 0,
},
duration: {
type: Number,
default: 1000,
},
height: {
type: Number,
default: 2,
},
color: {
type: String,
default: 'var(--loading-bar-gradient)',
},
})
const indicator = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle,
})
onBeforeUnmount(() => indicator.clear)
const loading = useLoading()
watch(loading, (newValue) => {
if (newValue.barEnabled) {
if (newValue.loading) {
indicator.start()
} else {
indicator.finish()
}
}
})
function useLoadingIndicator(opts) {
const progress = ref(0)
const isLoading = ref(false)
const step = computed(() => 10000 / opts.duration)
let _timer = null
let _throttle = null
function start() {
clear()
progress.value = 0
if (opts.throttle) {
_throttle = setTimeout(() => {
isLoading.value = true
_startTimer()
}, opts.throttle)
} else {
isLoading.value = true
_startTimer()
}
}
function finish() {
progress.value = 100
_hide()
}
function clear() {
clearInterval(_timer)
clearTimeout(_throttle)
_timer = null
_throttle = null
}
function _increase(num) {
progress.value = Math.min(100, progress.value + num)
}
function _hide() {
clear()
setTimeout(() => {
isLoading.value = false
setTimeout(() => {
progress.value = 0
}, 400)
}, 500)
}
function _startTimer() {
_timer = setInterval(() => {
_increase(step.value)
}, 100)
}
return { progress, isLoading, start, finish, clear }
}
</script>
<template>
<div
class="loading-indicator-bar"
:style="{
'--_width': `${indicator.progress.value}%`,
'--_height': `${indicator.isLoading.value ? props.height : 0}px`,
'--_opacity': `${indicator.isLoading.value ? 1 : 0}`,
top: `0`,
right: `0`,
left: `${props.offsetWidth}`,
pointerEvents: 'none',
width: `var(--_width)`,
height: `var(--_height)`,
borderRadius: `var(--_height)`,
// opacity: `var(--_opacity)`,
background: `${props.color}`,
backgroundSize: `${(100 / indicator.progress.value) * 100}% auto`,
transition: 'width 0.1s ease-in-out, height 0.1s ease-out',
zIndex: 6,
}"
>
<slot />
</div>
</template>
<style lang="scss" scoped>
.loading-indicator-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
width: var(--_width);
bottom: 0;
background-image: radial-gradient(80% 100% at 20% 0%, var(--color-brand) 0%, transparent 80%);
opacity: calc(var(--_opacity) * 0.1);
z-index: 5;
transition:
width 0.1s ease-in-out,
opacity 0.1s ease-out;
}
</style>
@@ -3,7 +3,6 @@
ref="outerRef"
data-tauri-drag-region
class="min-w-0 overflow-hidden pl-3"
:class="{ 'breadcrumb-fade-mask': isOverflowing }"
:style="isOverflowing ? { '--scroll-distance': `-${overflowAmount}px` } : undefined"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
@@ -129,16 +128,6 @@ watch(breadcrumbs, () => {
</script>
<style scoped>
.breadcrumb-fade-mask {
mask-image: linear-gradient(
to right,
transparent,
black 12px,
black calc(100% - 12px),
transparent
);
}
.breadcrumbs-scroll {
animation: breadcrumb-scroll 10s ease-in-out infinite;
}
@@ -6,7 +6,6 @@ import {
HammerIcon,
LogInIcon,
UpdatedIcon,
WrenchIcon,
XIcon,
} from '@modrinth/assets'
import { ButtonStyled, Collapsible, injectNotificationManager } from '@modrinth/ui'
@@ -156,7 +155,7 @@ async function copyToClipboard(text) {
<template>
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
<div class="modal-body max-w-[550px]">
<div class="modal-body">
<div class="markdown-body">
<template v-if="errorType === 'minecraft_auth'">
<template v-if="metadata.network">
@@ -281,7 +280,7 @@ async function copyToClipboard(text) {
{{ debugInfo }}
</template>
<template v-if="hasDebugInfo">
<div class="w-full h-[1px] bg-surface-5 mb-3"></div>
<hr />
<p>
If nothing is working and you need help, visit
<a :href="supportLink">our support page</a>
@@ -297,47 +296,28 @@ async function copyToClipboard(text) {
<ButtonStyled v-if="closable">
<button @click="errorModal.hide()"><XIcon /> Close</button>
</ButtonStyled>
<ButtonStyled v-if="hasDebugInfo">
<button :disabled="copied" @click="copyToClipboard(debugInfo)">
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
<template v-else> <CopyIcon /> Copy debug info </template>
</button>
</ButtonStyled>
</div>
<template v-if="hasDebugInfo">
<div class="flex flex-col gap-2">
<div class="w-full h-[1px] bg-surface-5"></div>
<div class="overflow-clip">
<button
class="flex items-center justify-between w-full bg-transparent border-0 py-4 cursor-pointer"
@click="errorCollapsed = !errorCollapsed"
>
<span class="flex items-center gap-2 text-contrast font-extrabold m-0">
<WrenchIcon class="h-4 w-4" />
Debug information
</span>
<DropdownIcon
class="h-5 w-5 text-secondary transition-transform"
:class="{ 'rotate-180': !errorCollapsed }"
/>
</button>
<Collapsible :collapsed="errorCollapsed">
<div
class="p-3 bg-surface-2 rounded-2xl text-xs grid grid-cols-[1fr_auto] max-w-full items-start"
>
<div
class="m-0 p-0 rounded-none bg-transparent text-sm font-mono break-words overflow-auto"
>
{{ debugInfo }}
</div>
<ButtonStyled circular>
<button
v-tooltip="'Copy debug info'"
:disabled="copied"
@click="copyToClipboard(debugInfo)"
>
<template v-if="copied"> <CheckIcon class="text-green" /> </template>
<template v-else> <CopyIcon /> </template>
</button>
</ButtonStyled>
</div>
</Collapsible>
</div>
<div class="bg-button-bg rounded-xl mt-2 overflow-clip">
<button
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
@click="errorCollapsed = !errorCollapsed"
>
<span class="text-contrast font-extrabold m-0">Debug information:</span>
<DropdownIcon
class="h-5 w-5 text-secondary transition-transform"
:class="{ 'rotate-180': !errorCollapsed }"
/>
</button>
<Collapsible :collapsed="errorCollapsed">
<pre class="m-0 px-4 py-3 bg-bg rounded-none">{{ debugInfo }}</pre>
</Collapsible>
</div>
</template>
</div>
@@ -2,7 +2,6 @@
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, FormattedTag } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { computed } from 'vue'
type Instance = {
game_version: string
@@ -13,23 +12,18 @@ type Instance = {
name: string
}
const props = withDefaults(
defineProps<{
instance: Instance
backTab?: string
}>(),
{ backTab: undefined },
)
const instanceLink = computed(() => {
const base = `/instance/${encodeURIComponent(props.instance.path)}`
return props.backTab ? `${base}/${props.backTab}` : base
})
defineProps<{
instance: Instance
}>()
</script>
<template>
<div class="flex justify-between items-center border-0 border-b border-solid border-divider pb-4">
<router-link :to="instanceLink" tabindex="-1" class="flex flex-col gap-4 text-primary">
<router-link
:to="`/instance/${encodeURIComponent(instance.path)}`"
tabindex="-1"
class="flex flex-col gap-4 text-primary"
>
<span class="flex items-center gap-2">
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : undefined"
@@ -49,7 +43,9 @@ const instanceLink = computed(() => {
</span>
</router-link>
<ButtonStyled>
<router-link :to="instanceLink"> <LeftArrowIcon /> Back to instance </router-link>
<router-link :to="`/instance/${encodeURIComponent(instance.path)}`">
<LeftArrowIcon /> Back to instance
</router-link>
</ButtonStyled>
</div>
</template>
@@ -3,7 +3,6 @@
v-if="typeof to === 'string'"
:to="to"
v-bind="$attrs"
:active-class="isSubpage ? '' : undefined"
:class="{
'router-link-active': isPrimary && isPrimary(route),
'subpage-active': isSubpage && isSubpage(route),
@@ -0,0 +1,185 @@
<template>
<nav
v-if="filteredLinks.length > 1"
ref="scrollContainer"
class="card-shadow experimental-styles-within relative flex w-fit overflow-clip rounded-full bg-bg-raised p-1 text-sm font-bold"
>
<RouterLink
v-for="(link, index) in filteredLinks"
v-show="link.shown === undefined ? true : link.shown"
:key="index"
ref="tabLinkElements"
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
:class="`button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full ${activeIndex === index && !subpageSelected ? 'text-button-textSelected' : activeIndex === index && subpageSelected ? 'text-contrast' : 'text-primary'}`"
>
<component :is="link.icon" v-if="link.icon" class="size-5" />
<span class="text-nowrap">{{ link.label }}</span>
</RouterLink>
<div
:class="[
'pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1',
subpageSelected ? 'bg-button-bg' : 'bg-button-bgSelected',
{ 'navtabs-transition': transitionsEnabled },
]"
:style="{
left: sliderLeftPx,
top: sliderTopPx,
right: sliderRightPx,
bottom: sliderBottomPx,
opacity: sliderReady && activeIndex !== -1 ? 1 : 0,
}"
aria-hidden="true"
></div>
</nav>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import type { RouteLocationRaw } from 'vue-router'
import { RouterLink, useRoute } from 'vue-router'
const route = useRoute()
interface Tab {
label: string
href: string | RouteLocationRaw
shown?: boolean
icon?: unknown
subpages?: string[]
}
const props = defineProps<{
links: Tab[]
query?: string
}>()
const scrollContainer = ref<HTMLElement | null>(null)
const sliderLeft = ref(4)
const sliderTop = ref(4)
const sliderRight = ref(4)
const sliderBottom = ref(4)
const activeIndex = ref(-1)
const subpageSelected = ref(false)
const sliderReady = ref(false)
const transitionsEnabled = ref(false)
const sliderDelays = ref({ left: '0ms', top: '0ms', right: '0ms', bottom: '0ms' })
const filteredLinks = computed(() =>
props.links.filter((x) => (x.shown === undefined ? true : x.shown)),
)
const sliderLeftPx = computed(() => `${sliderLeft.value}px`)
const sliderTopPx = computed(() => `${sliderTop.value}px`)
const sliderRightPx = computed(() => `${sliderRight.value}px`)
const sliderBottomPx = computed(() => `${sliderBottom.value}px`)
const leftDelay = computed(() => sliderDelays.value.left)
const rightDelay = computed(() => sliderDelays.value.right)
const topDelay = computed(() => sliderDelays.value.top)
const bottomDelay = computed(() => sliderDelays.value.bottom)
function pickLink() {
let index = -1
subpageSelected.value = false
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
const link = filteredLinks.value[i]
if (route.path === (typeof link.href === 'string' ? link.href : link.href.path)) {
index = i
break
} else if (link.subpages && link.subpages.some((subpage) => route.path.includes(subpage))) {
index = i
subpageSelected.value = true
break
}
}
activeIndex.value = index
if (activeIndex.value !== -1) {
startAnimation()
} else {
sliderLeft.value = 0
sliderRight.value = 0
}
}
function getTabElement(index: number): HTMLElement | null {
if (index === -1) return null
const container = scrollContainer.value
if (!container) return null
const tabs = container.querySelectorAll('.button-animation')
return (tabs[index] as HTMLElement) ?? null
}
function startAnimation() {
const el = getTabElement(activeIndex.value)
if (!el?.offsetParent) return
const parent = el.offsetParent as HTMLElement
const newValues = {
left: el.offsetLeft,
top: el.offsetTop,
right: parent.offsetWidth - el.offsetLeft - el.offsetWidth,
bottom: parent.offsetHeight - el.offsetTop - el.offsetHeight,
}
const isInitialPosition = sliderLeft.value === 4 && sliderRight.value === 4
if (isInitialPosition) {
sliderLeft.value = newValues.left
sliderRight.value = newValues.right
sliderTop.value = newValues.top
sliderBottom.value = newValues.bottom
sliderReady.value = true
requestAnimationFrame(() => {
transitionsEnabled.value = true
})
} else {
const STAGGER_DELAY = '200ms'
sliderDelays.value = {
left: newValues.left < sliderLeft.value ? '0ms' : STAGGER_DELAY,
right: newValues.left < sliderLeft.value ? STAGGER_DELAY : '0ms',
top: newValues.top < sliderTop.value ? '0ms' : STAGGER_DELAY,
bottom: newValues.top < sliderTop.value ? STAGGER_DELAY : '0ms',
}
sliderLeft.value = newValues.left
sliderRight.value = newValues.right
sliderTop.value = newValues.top
sliderBottom.value = newValues.bottom
}
}
onMounted(async () => {
window.addEventListener('resize', pickLink)
await nextTick()
pickLink()
})
onUnmounted(() => {
window.removeEventListener('resize', pickLink)
})
watch(
filteredLinks,
async () => {
await nextTick()
pickLink()
},
{ deep: true },
)
watch(route, async () => {
await nextTick()
pickLink()
})
</script>
<style scoped>
.navtabs-transition {
/* Delay on opacity is to hide any jankiness as the page loads */
transition:
left 150ms cubic-bezier(0.4, 0, 0.2, 1) v-bind(leftDelay),
right 150ms cubic-bezier(0.4, 0, 0.2, 1) v-bind(rightDelay),
top 150ms cubic-bezier(0.4, 0, 0.2, 1) v-bind(topDelay),
bottom 150ms cubic-bezier(0.4, 0, 0.2, 1) v-bind(bottomDelay),
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
}
</style>
@@ -0,0 +1,142 @@
<template>
<ProjectCard
:title="project.name"
:link="
() => {
emit('open')
$router.push({
path: `/project/${project.project_id ?? project.id}`,
query: { i: props.instance ? props.instance.path : undefined },
})
}
"
:author="{ name: project.author, link: `https://modrinth.com/user/${project.author}` }"
:icon-url="project.icon_url"
:summary="project.summary"
:tags="project.display_categories"
:all-tags="project.categories"
:downloads="project.downloads"
:followers="project.follows"
:date-updated="project.date_modified"
:banner="project.featured_gallery ?? undefined"
:color="project.color ?? undefined"
:environment="
['mod', 'modpack'].includes(projectType)
? {
clientSide: project.client_side?.[0],
serverSide: project.server_side?.[0],
}
: undefined
"
layout="list"
>
<template #actions>
<ButtonStyled color="brand" type="outlined">
<button
:disabled="installed || installing"
class="shrink-0 no-wrap"
@click.stop="install()"
>
<SpinnerIcon v-if="installing" class="animate-spin" />
<template v-else-if="!installed">
<DownloadIcon v-if="modpack || instance" />
<PlusIcon v-else />
</template>
<CheckIcon v-else />
{{
installing
? 'Installing'
: installed
? 'Installed'
: modpack || instance
? 'Install'
: 'Add to an instance'
}}
</button>
</ButtonStyled>
</template>
</ProjectCard>
</template>
<script setup>
import { CheckIcon, DownloadIcon, PlusIcon, SpinnerIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, ProjectCard } from '@modrinth/ui'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { injectContentInstall } from '@/providers/content-install'
const { install: installVersion } = injectContentInstall()
dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const router = useRouter()
const props = defineProps({
backgroundImage: {
type: String,
default: null,
},
project: {
type: Object,
required: true,
},
instance: {
type: Object,
default: null,
},
featured: {
type: Boolean,
default: false,
},
installed: {
type: Boolean,
default: false,
},
projectType: {
type: String,
default: undefined,
},
activeLoader: {
type: String,
default: null,
},
activeGameVersion: {
type: String,
default: null,
},
})
const emit = defineEmits(['open', 'install'])
const installing = ref(false)
async function install() {
installing.value = true
await installVersion(
props.project.project_id ?? props.project.id,
null,
props.instance ? props.instance.path : null,
'SearchCard',
(versionId) => {
installing.value = false
if (versionId) {
emit('install', props.project.project_id ?? props.project.id)
}
},
(profile) => {
router.push(`/instance/${profile}`)
},
{
preferredLoader: props.activeLoader ?? undefined,
preferredGameVersion: props.activeGameVersion ?? undefined,
},
).catch(handleError)
}
const modpack = computed(() => props.project.project_types?.includes('modpack'))
</script>
File diff suppressed because one or more lines are too long
@@ -1,9 +1,11 @@
<script setup>
import { Button, injectNotificationManager, ProjectCard } from '@modrinth/ui'
import { Button, injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_project_v3, get_version } from '@/helpers/cache.js'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get_project, get_version } from '@/helpers/cache.js'
import { get_categories } from '@/helpers/tags.js'
import { injectContentInstall } from '@/providers/content-install'
const { handleError } = injectNotificationManager()
@@ -12,22 +14,26 @@ const { install: installVersion } = injectContentInstall()
const confirmModal = ref(null)
const project = ref(null)
const version = ref(null)
const categories = ref(null)
const installing = ref(false)
defineExpose({
async show(event) {
if (event.event === 'InstallVersion') {
version.value = await get_version(event.id, 'must_revalidate').catch(handleError)
project.value = await get_project_v3(version.value.project_id, 'must_revalidate').catch(
project.value = await get_project(version.value.project_id, 'must_revalidate').catch(
handleError,
)
} else {
project.value = await get_project_v3(event.id, 'must_revalidate').catch(handleError)
project.value = await get_project(event.id, 'must_revalidate').catch(handleError)
version.value = await get_version(
project.value.versions[project.value.versions.length - 1],
'must_revalidate',
).catch(handleError)
}
categories.value = (await get_categories().catch(handleError)).filter(
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod',
)
confirmModal.value.show()
},
})
@@ -46,22 +52,13 @@ async function install() {
</script>
<template>
<ModalWrapper ref="confirmModal" :header="`Install ${project?.name}`">
<ModalWrapper ref="confirmModal" :header="`Install ${project?.title}`">
<div class="modal-body">
<ProjectCard
:title="project.name"
:link="() => confirmModal.hide()"
:icon-url="project.icon_url"
:summary="project.summary"
:tags="project.display_categories"
:all-tags="project.categories"
:downloads="project.downloads"
:followers="project.follows"
:date-updated="project.date_modified"
:banner="project.featured_gallery ?? undefined"
:color="project.color ?? undefined"
layout="list"
<SearchCard
:project="project"
class="project-card"
:categories="categories"
@open="confirmModal.hide()"
/>
<div class="button-row">
<div class="markdown-body">
@@ -1,98 +0,0 @@
<template>
<section v-if="showControls" class="window-controls" data-tauri-drag-region-exclude>
<ButtonStyled type="transparent" circular>
<button class="titlebar-button" @click="() => getCurrentWindow().minimize()">
<MinimizeIcon />
</button>
</ButtonStyled>
<ButtonStyled type="transparent" circular>
<button class="titlebar-button" @click="() => getCurrentWindow().toggleMaximize()">
<RestoreIcon v-if="isMaximized" />
<MaximizeIcon v-else />
</button>
</ButtonStyled>
<ButtonStyled type="transparent" circular>
<button class="titlebar-button close" @click="handleClose">
<XIcon />
</button>
</ButtonStyled>
</section>
</template>
<script setup>
import { MaximizeIcon, MinimizeIcon, RestoreIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { get as getSettings } from '@/helpers/settings.ts'
import { getOS } from '@/helpers/utils.js'
const WINDOW_CONTROLS_WIDTH = '8rem'
const nativeDecorations = ref(true)
const isMaximized = ref(false)
const os = ref('')
const showControls = computed(() => !nativeDecorations.value && os.value !== 'MacOS')
watch(
showControls,
(visible) => {
if (typeof document === 'undefined') return
if (visible) {
document.documentElement.style.setProperty('--window-controls-width', WINDOW_CONTROLS_WIDTH)
} else {
document.documentElement.style.removeProperty('--window-controls-width')
}
},
{ immediate: true },
)
onMounted(async () => {
os.value = await getOS()
const settings = await getSettings()
nativeDecorations.value = settings.native_decorations
if (os.value !== 'MacOS') {
await getCurrentWindow().setDecorations(nativeDecorations.value)
}
isMaximized.value = await getCurrentWindow().isMaximized()
const unlisten = await getCurrentWindow().onResized(async () => {
isMaximized.value = await getCurrentWindow().isMaximized()
})
onUnmounted(() => {
unlisten()
document.documentElement.style.removeProperty('--window-controls-width')
})
})
const handleClose = async () => {
await saveWindowState(StateFlags.ALL)
await getCurrentWindow().close()
}
</script>
<style lang="scss" scoped>
.window-controls {
position: fixed;
top: 0;
right: 0;
z-index: 10001;
display: flex;
flex-direction: row;
align-items: center;
height: var(--top-bar-height, 3rem);
padding-right: 0.5rem;
gap: 0.25rem;
.titlebar-button.close:hover {
background-color: var(--color-red);
color: var(--color-accent-contrast);
}
}
</style>
@@ -288,7 +288,7 @@ const messages = defineMessages({
</div>
</div>
</ModalWrapper>
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 -ml-1">
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 ml-2 mr-1">
<template v-if="sortedFriends.length > 0">
<ButtonStyled circular type="transparent">
<button
@@ -309,7 +309,7 @@ const messages = defineMessages({
@keyup.esc="search = ''"
/>
</template>
<h3 v-else class="w-full text-base text-primary font-medium m-0">
<h3 v-else class="ml-2 w-full text-base text-primary font-medium m-0">
{{ formatMessage(messages.friends) }}
</h3>
<ButtonStyled v-if="incomingRequests.length > 0" circular type="transparent">
@@ -331,11 +331,11 @@ const messages = defineMessages({
</ButtonStyled>
</div>
<div class="flex flex-col gap-3">
<h3 v-if="loading" class="text-base text-primary font-medium m-0">
<h3 v-if="loading" class="ml-4 mr-1 text-base text-primary font-medium m-0">
{{ formatMessage(messages.friends) }}
</h3>
<template v-if="loading">
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse">
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse ml-4 mr-1">
<div class="min-w-9 min-h-9 bg-button-bg rounded-full"></div>
<div class="flex flex-col w-full">
<div class="h-3 bg-button-bg rounded-full w-1/2 mb-1"></div>
@@ -344,7 +344,7 @@ const messages = defineMessages({
</div>
</template>
<template v-else-if="sortedFriends.length === 0">
<div class="text-sm">
<div class="text-sm ml-4 mr-1">
<div v-if="!userCredentials">
<IntlFormatted :message-id="messages.signInToAddFriends">
<template #link="{ children }">
@@ -106,7 +106,7 @@ const messages = defineMessages({
:open-by-default="openByDefault"
:force-open="isSearching"
:button-class="
'flex w-full items-center bg-transparent border-0 p-0' +
'pl-4 pr-3 flex w-full items-center bg-transparent border-0 p-0' +
(isSearching
? ''
: ' cursor-pointer hover:brightness-[--hover-brightness] active:scale-[0.98] transition-all')
@@ -122,7 +122,7 @@ const messages = defineMessages({
<div
v-for="friend in friends"
:key="friend.username"
class="group grid items-center grid-cols-[auto_1fr_auto] gap-2 hover:bg-button-bg transition-colors rounded-full mr-1"
class="group grid items-center grid-cols-[auto_1fr_auto] gap-2 hover:bg-button-bg transition-colors rounded-full ml-4 mr-1"
@contextmenu.prevent.stop="
(event) => friendOptions?.showMenu(event, friend, createContextMenuOptions(friend))
"
@@ -30,19 +30,19 @@ const deleteConfirmModal = ref()
const { instance } = injectInstanceSettings()
const title = ref(instance.value.name)
const icon: Ref<string | undefined> = ref(instance.value.icon_path)
const groups = ref([...instance.value.groups])
const title = ref(instance.name)
const icon: Ref<string | undefined> = ref(instance.icon_path)
const groups = ref(instance.groups)
const newCategoryInput = ref('')
const installing = computed(() => instance.value.install_stage !== 'installed')
const installing = computed(() => instance.install_stage !== 'installed')
async function duplicateProfile() {
await duplicate(instance.value.path).catch(handleError)
await duplicate(instance.path).catch(handleError)
trackEvent('InstanceDuplicate', {
loader: instance.value.loader,
game_version: instance.value.game_version,
loader: instance.loader,
game_version: instance.game_version,
})
}
@@ -53,7 +53,7 @@ const availableGroups = computed(() => [
async function resetIcon() {
icon.value = undefined
await edit_icon(instance.value.path, null).catch(handleError)
await edit_icon(instance.path, null).catch(handleError)
trackEvent('InstanceRemoveIcon')
}
@@ -71,7 +71,7 @@ async function setIcon() {
if (!value) return
icon.value = value
await edit_icon(instance.value.path, icon.value).catch(handleError)
await edit_icon(instance.path, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')
}
@@ -102,7 +102,7 @@ watch(
[title, groups, groups],
async () => {
if (removing.value) return
await edit(instance.value.path, editProfileObject.value).catch(handleError)
await edit(instance.path, editProfileObject.value).catch(handleError)
},
{ deep: true },
)
@@ -110,11 +110,11 @@ watch(
const removing = ref(false)
async function removeProfile() {
removing.value = true
const path = instance.value.path
const path = instance.path
trackEvent('InstanceRemove', {
loader: instance.value.loader,
game_version: instance.value.game_version,
loader: instance.loader,
game_version: instance.game_version,
})
await router.push({ path: '/' })
@@ -198,48 +198,45 @@ const messages = defineMessages({
<template>
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="removeProfile" />
<div class="block">
<div class="float-end ml-10 relative group w-fit">
<div class="flex flex-col gap-1">
<span class="text-lg font-semibold text-contrast">Icon</span>
<div class="group relative w-fit">
<OverflowMenu
v-tooltip="formatMessage(messages.editIcon)"
class="bg-transparent border-none appearance-none p-0 m-0 cursor-pointer group-active:scale-95 transition-transform"
:options="[
{
id: 'select',
action: () => setIcon(),
},
{
id: 'remove',
color: 'danger',
action: () => resetIcon(),
shown: !!icon,
},
]"
<div class="float-end ml-4 relative group">
<OverflowMenu
v-tooltip="formatMessage(messages.editIcon)"
class="bg-transparent border-none appearance-none p-0 m-0 cursor-pointer group-active:scale-95 transition-transform"
:options="[
{
id: 'select',
action: () => setIcon(),
},
{
id: 'remove',
color: 'danger',
action: () => resetIcon(),
shown: !!icon,
},
]"
>
<Avatar
:src="icon ? convertFileSrc(icon) : icon"
size="108px"
class="!border-4 group-hover:brightness-75"
:tint-by="instance.path"
no-shadow
/>
<div class="absolute top-0 right-0 m-2">
<div
class="p-2 m-0 text-primary flex items-center justify-center aspect-square bg-button-bg rounded-full border-button-border border-solid border-[1px] hovering-icon-shadow"
>
<Avatar
:src="icon ? convertFileSrc(icon) : icon"
size="108px"
class="transition-[filter] group-hover:brightness-75"
:tint-by="instance.path"
no-shadow
/>
<div
class="absolute top-0 h-full w-full flex items-center justify-center opacity-0 transition-all group-hover:opacity-100"
>
<EditIcon aria-hidden="true" class="h-10 w-10 text-primary" />
</div>
<template #select>
<UploadIcon />
{{ icon ? formatMessage(messages.replaceIcon) : formatMessage(messages.selectIcon) }}
</template>
<template #remove> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
</OverflowMenu>
<EditIcon aria-hidden="true" class="h-4 w-4 text-primary" />
</div>
</div>
</div>
<template #select>
<UploadIcon />
{{ icon ? formatMessage(messages.replaceIcon) : formatMessage(messages.selectIcon) }}
</template>
<template #remove> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
</OverflowMenu>
</div>
<label for="instance-name" class="m-0 text-lg font-semibold text-contrast block">
<label for="instance-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.name) }}
</label>
<div class="flex">
@@ -252,82 +249,76 @@ const messages = defineMessages({
/>
</div>
<template v-if="instance.install_stage == 'installed'">
<div class="flex flex-col gap-2.5 mt-6">
<h2 id="duplicate-instance-label" class="m-0 text-lg font-semibold text-contrast block">
<div>
<h2
id="duplicate-instance-label"
class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block"
>
{{ formatMessage(messages.duplicateInstance) }}
</h2>
<ButtonStyled>
<button
v-tooltip="installing ? formatMessage(messages.duplicateButtonTooltipInstalling) : null"
aria-labelledby="duplicate-instance-label"
:disabled="installing"
class="w-max !shadow-none"
@click="duplicateProfile"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</button>
</ButtonStyled>
<p class="m-0">
<p class="m-0 mb-2">
{{ formatMessage(messages.duplicateInstanceDescription) }}
</p>
</div>
</template>
<div class="flex flex-col gap-2.5 mt-6">
<h2 class="m-0 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.libraryGroups) }}
</h2>
<div class="flex flex-col gap-1">
<Checkbox
v-for="group in availableGroups"
:key="group"
:model-value="groups.includes(group)"
:label="group"
@click="toggleGroup(group)"
/>
<div class="flex gap-2 items-center">
<StyledInput
v-model="newCategoryInput"
:placeholder="formatMessage(messages.libraryGroupsEnterName)"
class="w-full max-w-[300px]"
@submit="() => addCategory"
/>
<ButtonStyled>
<button class="w-fit !shadow-none" @click="() => addCategory()">
<PlusIcon /> {{ formatMessage(messages.libraryGroupsCreate) }}
</button>
</ButtonStyled>
</div>
</div>
<p class="m-0">
{{ formatMessage(messages.libraryGroupsDescription) }}
</p>
</div>
<div class="flex flex-col gap-2.5 mt-6">
<h2 id="delete-instance-label" class="m-0 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.deleteInstance) }}
</h2>
<ButtonStyled color="red">
<ButtonStyled>
<button
aria-labelledby="delete-instance-label"
:disabled="removing"
class="w-fit !shadow-none"
@click="deleteConfirmModal.show()"
v-tooltip="installing ? formatMessage(messages.duplicateButtonTooltipInstalling) : null"
aria-labelledby="duplicate-instance-label"
:disabled="installing"
@click="duplicateProfile"
>
<SpinnerIcon v-if="removing" class="animate-spin" />
<TrashIcon v-else />
{{
removing
? formatMessage(messages.deletingInstanceButton)
: formatMessage(messages.deleteInstanceButton)
}}
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</button>
</ButtonStyled>
<p class="m-0">
{{ formatMessage(messages.deleteInstanceDescription) }}
</p>
</template>
<h2 class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.libraryGroups) }}
</h2>
<p class="m-0 mb-2">
{{ formatMessage(messages.libraryGroupsDescription) }}
</p>
<div class="flex flex-col gap-1">
<Checkbox
v-for="group in availableGroups"
:key="group"
:model-value="groups.includes(group)"
:label="group"
@click="toggleGroup(group)"
/>
<div class="flex gap-2 items-center">
<StyledInput
v-model="newCategoryInput"
:placeholder="formatMessage(messages.libraryGroupsEnterName)"
@submit="() => addCategory"
/>
<ButtonStyled>
<button class="w-fit" @click="() => addCategory()">
<PlusIcon /> {{ formatMessage(messages.libraryGroupsCreate) }}
</button>
</ButtonStyled>
</div>
</div>
<h2 id="delete-instance-label" class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.deleteInstance) }}
</h2>
<p class="m-0 mb-2">
{{ formatMessage(messages.deleteInstanceDescription) }}
</p>
<ButtonStyled color="red">
<button
aria-labelledby="delete-instance-label"
:disabled="removing"
@click="deleteConfirmModal.show()"
>
<SpinnerIcon v-if="removing" class="animate-spin" />
<TrashIcon v-else />
{{
removing
? formatMessage(messages.deletingInstanceButton)
: formatMessage(messages.deleteInstanceButton)
}}
</button>
</ButtonStyled>
</div>
</template>
<style scoped lang="scss">
@@ -22,11 +22,9 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideHooks = ref(
!!instance.value.hooks.pre_launch ||
!!instance.value.hooks.wrapper ||
!!instance.value.hooks.post_exit,
!!instance.hooks.pre_launch || !!instance.hooks.wrapper || !!instance.hooks.post_exit,
)
const hooks = ref(instance.value.hooks ?? globalSettings.hooks)
const hooks = ref(instance.hooks ?? globalSettings.hooks)
const editProfileObject = computed(() => {
const editProfile: {
@@ -42,7 +40,7 @@ const editProfileObject = computed(() => {
watch(
[overrideHooks, hooks],
async () => {
await edit(instance.value.path, editProfileObject.value)
await edit(instance.path, editProfileObject.value)
},
{ deep: true },
)
@@ -101,57 +99,57 @@ const messages = defineMessages({
<template>
<div>
<h2 class="m-0 m-0 text-lg font-semibold text-contrast">
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.hooks) }}
</h2>
<Checkbox v-model="overrideHooks" :label="formatMessage(messages.customHooks)" class="my-2.5" />
<p class="m-0">
{{ formatMessage(messages.hooksDescription) }}
</p>
<Checkbox v-model="overrideHooks" :label="formatMessage(messages.customHooks)" class="mt-2" />
<h2 class="mt-6 m-0 text-lg font-semibold text-contrast">
<h2 class="mt-2 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.preLaunch) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.preLaunchDescription) }}
</p>
<StyledInput
id="pre-launch"
v-model="hooks.pre_launch"
autocomplete="off"
:disabled="!overrideHooks"
:placeholder="formatMessage(messages.preLaunchEnter)"
wrapper-class="w-full my-2.5"
wrapper-class="w-full mt-2"
/>
<p class="m-0">
{{ formatMessage(messages.preLaunchDescription) }}
</p>
<h2 class="mt-6 m-0 text-lg font-semibold text-contrast">
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.wrapper) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.wrapperDescription) }}
</p>
<StyledInput
id="wrapper"
v-model="hooks.wrapper"
autocomplete="off"
:disabled="!overrideHooks"
:placeholder="formatMessage(messages.wrapperEnter)"
wrapper-class="w-full my-2.5"
wrapper-class="w-full mt-2"
/>
<p class="m-0">
{{ formatMessage(messages.wrapperDescription) }}
</p>
<h2 class="mt-6 m-0 text-lg font-semibold text-contrast">
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.postExit) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.postExitDescription) }}
</p>
<StyledInput
id="post-exit"
v-model="hooks.post_exit"
autocomplete="off"
:disabled="!overrideHooks"
:placeholder="formatMessage(messages.postExitEnter)"
wrapper-class="w-full my-2.5"
wrapper-class="w-full mt-2"
/>
<p class="m-0">
{{ formatMessage(messages.postExitDescription) }}
</p>
</div>
</template>
@@ -73,9 +73,9 @@ const [
])
const { data: modpackInfo } = useQuery({
queryKey: computed(() => ['linkedModpackInfo', instance.value.path]),
queryFn: () => get_linked_modpack_info(instance.value.path, 'must_revalidate'),
enabled: computed(() => !!instance.value.linked_data?.project_id && !offline),
queryKey: computed(() => ['linkedModpackInfo', instance.path]),
queryFn: () => get_linked_modpack_info(instance.path, 'must_revalidate'),
enabled: computed(() => !!instance.linked_data?.project_id && !offline),
})
const repairing = ref(false)
@@ -101,13 +101,13 @@ function getManifest(loader: string) {
provideAppBackup({
async createBackup() {
const allProfiles = await list()
const prefix = `${instance.value.name} - Backup #`
const prefix = `${instance.name} - Backup #`
const existingNums = allProfiles
.filter((p) => p.name.startsWith(prefix))
.map((p) => parseInt(p.name.slice(prefix.length), 10))
.filter((n) => !isNaN(n))
const nextNum = existingNums.length > 0 ? Math.max(...existingNums) + 1 : 1
const newPath = await duplicate(instance.value.path)
const newPath = await duplicate(instance.path)
await edit(newPath, { name: `${prefix}${nextNum}` })
},
})
@@ -118,30 +118,27 @@ provideInstallationSettings({
const rows = [
{
label: formatMessage(commonMessages.platformLabel),
value: formatLoaderLabel(instance.value.loader),
value: formatLoaderLabel(instance.loader),
},
{
label: formatMessage(commonMessages.gameVersionLabel),
value: instance.value.game_version,
value: instance.game_version,
},
]
if (instance.value.loader !== 'vanilla' && instance.value.loader_version) {
if (instance.loader !== 'vanilla' && instance.loader_version) {
rows.push({
label: formatMessage(messages.loaderVersion, {
loader: formatLoaderLabel(instance.value.loader),
loader: formatLoaderLabel(instance.loader),
}),
value: instance.value.loader_version,
value: instance.loader_version,
})
}
return rows
}),
isLinked: computed(() => !!instance.value.linked_data?.locked),
isLinked: computed(() => !!instance.linked_data?.locked),
isBusy: computed(
() =>
instance.value.install_stage !== 'installed' ||
repairing.value ||
reinstalling.value ||
!!offline,
instance.install_stage !== 'installed' || repairing.value || reinstalling.value || !!offline,
),
modpack: computed(() => {
if (!modpackInfo.value) return null
@@ -152,9 +149,9 @@ provideInstallationSettings({
versionNumber: modpackInfo.value.version?.version_number,
}
}),
currentPlatform: computed(() => instance.value.loader),
currentGameVersion: computed(() => instance.value.game_version),
currentLoaderVersion: computed(() => instance.value.loader_version ?? ''),
currentPlatform: computed(() => instance.loader),
currentGameVersion: computed(() => instance.game_version),
currentLoaderVersion: computed(() => instance.loader_version ?? ''),
availablePlatforms: loaders?.value?.map((x) => x.name) ?? [],
resolveGameVersions(loader, showSnapshots) {
@@ -197,50 +194,50 @@ provideInstallationSettings({
if (platform !== 'vanilla' && loaderVersionId) {
editProfile.loader_version = loaderVersionId
}
await edit(instance.value.path, editProfile).catch(handleError)
await edit(instance.path, editProfile).catch(handleError)
},
afterSave: async () => {
await install(instance.value.path, false).catch(handleError)
await install(instance.path, false).catch(handleError)
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
loader: instance.loader,
game_version: instance.game_version,
})
},
async repair() {
repairing.value = true
await install(instance.value.path, true).catch(handleError)
await install(instance.path, true).catch(handleError)
repairing.value = false
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
loader: instance.loader,
game_version: instance.game_version,
})
},
async reinstallModpack() {
reinstalling.value = true
await update_repair_modrinth(instance.value.path).catch(handleError)
await update_repair_modrinth(instance.path).catch(handleError)
reinstalling.value = false
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
loader: instance.loader,
game_version: instance.game_version,
})
},
async unlinkModpack() {
await edit(instance.value.path, {
await edit(instance.path, {
linked_data: null as unknown as undefined,
})
await queryClient.invalidateQueries({
queryKey: ['linkedModpackInfo', instance.value.path],
queryKey: ['linkedModpackInfo', instance.path],
})
onUnlinked()
},
getCachedModpackVersions: () => null,
async fetchModpackVersions() {
const versions = await get_project_versions(instance.value.linked_data!.project_id!).catch(
const versions = await get_project_versions(instance.linked_data!.project_id!).catch(
handleError,
)
return (versions ?? []) as Labrinth.Versions.v2.Version[]
@@ -253,20 +250,20 @@ provideInstallationSettings({
},
async onModpackVersionConfirm(version) {
await update_managed_modrinth_version(instance.value.path, version.id)
await update_managed_modrinth_version(instance.path, version.id)
await queryClient.invalidateQueries({
queryKey: ['linkedModpackInfo', instance.value.path],
queryKey: ['linkedModpackInfo', instance.path],
})
},
updaterModalProps: computed(() => ({
isApp: true,
currentVersionId:
modpackInfo.value?.update_version_id ?? instance.value.linked_data?.version_id ?? '',
modpackInfo.value?.update_version_id ?? instance.linked_data?.version_id ?? '',
projectIconUrl: modpackInfo.value?.project?.icon_url,
projectName: modpackInfo.value?.project?.title ?? 'Modpack',
currentGameVersion: instance.value.game_version,
currentLoader: instance.value.loader,
currentGameVersion: instance.game_version,
currentLoader: instance.loader,
})),
isServer: false,
@@ -25,24 +25,20 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
const overrideJavaInstall = ref(!!instance.value.java_path)
const optimalJava = readonly(await get_optimal_jre_key(instance.value.path).catch(handleError))
const javaInstall = ref({ path: optimalJava.path ?? instance.value.java_path })
const overrideJavaInstall = ref(!!instance.java_path)
const optimalJava = readonly(await get_optimal_jre_key(instance.path).catch(handleError))
const javaInstall = ref({ path: optimalJava.path ?? instance.java_path })
const overrideJavaArgs = ref((instance.value.extra_launch_args?.length ?? 0) > 0)
const javaArgs = ref(
(instance.value.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
)
const overrideJavaArgs = ref((instance.extra_launch_args?.length ?? 0) > 0)
const javaArgs = ref((instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '))
const overrideEnvVars = ref((instance.value.custom_env_vars?.length ?? 0) > 0)
const overrideEnvVars = ref((instance.custom_env_vars?.length ?? 0) > 0)
const envVars = ref(
(instance.value.custom_env_vars ?? globalSettings.custom_env_vars)
.map((x) => x.join('='))
.join(' '),
(instance.custom_env_vars ?? globalSettings.custom_env_vars).map((x) => x.join('=')).join(' '),
)
const overrideMemorySettings = ref(!!instance.value.memory)
const memory = ref(instance.value.memory ?? globalSettings.memory)
const overrideMemorySettings = ref(!!instance.memory)
const memory = ref(instance.memory ?? globalSettings.memory)
const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
maxMemory: number
snapPoints: number[]
@@ -80,7 +76,7 @@ watch(
memory,
],
async () => {
await edit(instance.value.path, editProfileObject.value)
await edit(instance.path, editProfileObject.value)
},
{ deep: true },
)
@@ -111,10 +107,10 @@ const messages = defineMessages({
<template>
<div>
<h2 id="project-name" class="m-0 mb-2.5 text-lg font-semibold text-contrast block">
<h2 id="project-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaInstallation) }}
</h2>
<Checkbox v-model="overrideJavaInstall" label="Custom Java installation" class="mb-2.5" />
<Checkbox v-model="overrideJavaInstall" label="Custom Java installation" class="mb-2" />
<template v-if="!overrideJavaInstall">
<div class="flex my-2 items-center gap-2 font-semibold">
<template v-if="javaInstall">
@@ -144,10 +140,10 @@ const messages = defineMessages({
</div>
</template>
<JavaSelector v-if="overrideJavaInstall || !javaInstall" v-model="javaInstall" />
<h2 id="project-name" class="mt-6 mb-2.5 text-lg font-semibold text-contrast block">
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaMemory) }}
</h2>
<Checkbox v-model="overrideMemorySettings" label="Custom memory allocation" class="mb-2.5" />
<Checkbox v-model="overrideMemorySettings" label="Custom memory allocation" class="mb-2" />
<Slider
id="max-memory"
v-model="memory.maximum"
@@ -159,7 +155,7 @@ const messages = defineMessages({
:snap-range="512"
unit="MB"
/>
<h2 id="project-name" class="mt-6 mb-2.5 text-lg font-semibold text-contrast block">
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaArguments) }}
</h2>
<Checkbox v-model="overrideJavaArgs" label="Custom java arguments" class="my-2" />
@@ -171,10 +167,10 @@ const messages = defineMessages({
placeholder="Enter java arguments..."
wrapper-class="w-full"
/>
<h2 id="project-name" class="mt-6 mb-2.5 text-lg font-semibold text-contrast block">
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaEnvironmentVariables) }}
</h2>
<Checkbox v-model="overrideEnvVars" label="Custom environment variables" class="mb-2.5" />
<Checkbox v-model="overrideEnvVars" label="Custom environment variables" class="mb-2" />
<StyledInput
id="env-vars"
v-model="envVars"
@@ -22,14 +22,12 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideWindowSettings = ref(
!!instance.value.game_resolution || !!instance.value.force_fullscreen,
)
const overrideWindowSettings = ref(!!instance.game_resolution || !!instance.force_fullscreen)
const resolution: Ref<[number, number]> = ref(
instance.value.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]),
instance.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]),
)
const fullscreenSetting: Ref<boolean> = ref(
instance.value.force_fullscreen ?? globalSettings.force_fullscreen,
instance.force_fullscreen ?? globalSettings.force_fullscreen,
)
const editProfileObject = computed(() => {
@@ -48,7 +46,7 @@ const editProfileObject = computed(() => {
watch(
[overrideWindowSettings, resolution, fullscreenSetting],
async () => {
await edit(instance.value.path, editProfileObject.value)
await edit(instance.path, editProfileObject.value)
},
{ deep: true },
)
@@ -94,14 +92,14 @@ const messages = defineMessages({
</script>
<template>
<div class="flex flex-col gap-6">
<div>
<Checkbox
v-model="overrideWindowSettings"
:label="formatMessage(messages.customWindowSettings)"
/>
<div class="flex items-center gap-4 justify-between">
<div class="flex flex-col gap-1">
<h2 class="m-0 text-lg font-semibold text-contrast">
<div class="mt-2 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.fullscreen) }}
</h2>
<p class="m-0">
@@ -120,9 +118,9 @@ const messages = defineMessages({
/>
</div>
<div class="flex items-center gap-4 justify-between">
<div class="flex flex-col gap-1">
<h2 class="m-0 text-lg font-semibold text-contrast">
<div class="mt-4 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.width) }}
</h2>
<p class="m-0">
@@ -139,9 +137,9 @@ const messages = defineMessages({
/>
</div>
<div class="flex items-center gap-4 justify-between">
<div class="flex flex-col gap-1">
<h2 class="m-0 text-lg font-semibold text-contrast">
<div class="mt-4 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.height) }}
</h2>
<p class="m-0">
@@ -6,13 +6,12 @@ import {
LanguagesIcon,
ModrinthIcon,
PaintbrushIcon,
ReportIcon,
SettingsIcon,
ShieldIcon,
ToggleRightIcon,
} from '@modrinth/assets'
import {
commonMessages,
commonSettingsMessages,
defineMessage,
defineMessages,
ProgressBar,
@@ -96,8 +95,11 @@ const tabs = [
content: ResourceManagementSettings,
},
{
name: commonSettingsMessages.featureFlags,
icon: ToggleRightIcon,
name: defineMessage({
id: 'app.settings.tabs.feature-flags',
defaultMessage: 'Feature flags',
}),
icon: ReportIcon,
content: FeatureFlagSettings,
developerOnly: true,
},
@@ -177,7 +179,7 @@ const messages = defineMessages({
>
<ModrinthIcon class="w-6 h-6" />
</button>
<div class="max-w-[200px]">
<div>
<p class="m-0">Modrinth App {{ version }}</p>
<p class="m-0">
<span v-if="osPlatform === 'macos'">macOS</span>
@@ -44,10 +44,8 @@ const emit = defineEmits<{
const isMinecraftServer = ref(false)
const handleUnlinked = () => emit('unlinked')
const instanceRef = computed(() => props.instance)
provideInstanceSettings({
instance: instanceRef,
instance: props.instance,
offline: props.offline,
isMinecraftServer,
onUnlinked: handleUnlinked,
@@ -1,31 +1,21 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.header)" fade="warning" max-width="500px">
<p class="m-0 text-secondary">
<IntlFormatted :message-id="messages.body" :values="{ instanceName }">
<template #bold="{ children }">
<span class="font-medium text-contrast"><component :is="() => children" /></span>
</template>
</IntlFormatted>
</p>
<Admonition type="warning" :header="formatMessage(messages.admonitionHeader)">
{{ formatMessage(messages.admonitionBody, { instanceName }) }}
</Admonition>
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="handleCancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="handleGoToInstance">
{{ formatMessage(messages.instance) }}
<RightArrowIcon />
<button class="!border !border-surface-4" @click="handleGoToInstance">
<ExternalIcon />
{{ formatMessage(messages.goToInstance) }}
</button>
</ButtonStyled>
<ButtonStyled color="orange">
<button @click="handleCreateAnyway">
<PlusIcon />
{{ formatMessage(messages.create) }}
{{ formatMessage(messages.createAnyway) }}
</button>
</ButtonStyled>
</div>
@@ -34,15 +24,8 @@
</template>
<script setup lang="ts">
import { PlusIcon, RightArrowIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
commonMessages,
defineMessages,
IntlFormatted,
NewModal,
useVIntl,
} from '@modrinth/ui'
import { ExternalIcon, PlusIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
import { ref } from 'vue'
const { formatMessage } = useVIntl()
@@ -52,18 +35,21 @@ const messages = defineMessages({
id: 'app.instance.modpack-already-installed.header',
defaultMessage: 'Modpack already installed',
},
body: {
id: 'app.instance.modpack-already-installed.body',
defaultMessage:
'This modpack is already installed in the <bold>{instanceName}</bold> instance. Are you sure you want to duplicate it?',
admonitionHeader: {
id: 'app.instance.modpack-already-installed.admonition-header',
defaultMessage: 'Duplicate modpack',
},
instance: {
id: 'app.instance.modpack-already-installed.instance',
defaultMessage: 'Instance',
admonitionBody: {
id: 'app.instance.modpack-already-installed.admonition-body',
defaultMessage: 'This modpack is already installed in the "{instanceName}" instance.',
},
create: {
id: 'app.instance.modpack-already-installed.create',
defaultMessage: 'Create',
goToInstance: {
id: 'app.instance.modpack-already-installed.go-to-instance',
defaultMessage: 'Go to instance',
},
createAnyway: {
id: 'app.instance.modpack-already-installed.create-anyway',
defaultMessage: 'Create anyway',
},
})
@@ -82,10 +68,6 @@ function show(name: string, path: string) {
modal.value?.show()
}
function handleCancel() {
modal.value?.hide()
}
function handleGoToInstance() {
modal.value?.hide()
emit('go-to-instance', instancePath.value)
@@ -21,7 +21,7 @@ watch(
)
</script>
<template>
<h2 class="m-0 text-lg font-semibold text-contrast">Color theme</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Color theme</h2>
<p class="m-0 mt-1">Select your preferred color theme for Modrinth App.</p>
<ThemeSelector
@@ -36,9 +36,9 @@ watch(
system-theme-color="system"
/>
<div class="mt-6 flex items-center justify-between">
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Advanced rendering</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Advanced rendering</h2>
<p class="m-0 mt-1">
Enables advanced rendering such as blur effects that may cause performance issues without
hardware-accelerated rendering.
@@ -57,48 +57,48 @@ watch(
/>
</div>
<div class="mt-6 flex items-center justify-between">
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Hide nametag</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Hide nametag</h2>
<p class="m-0 mt-1">Disables the nametag above your player on the skins page.</p>
</div>
<Toggle id="hide-nametag-skins-page" v-model="settings.hide_nametag_skins_page" />
</div>
<div v-if="os !== 'MacOS'" class="mt-6 flex items-center justify-between gap-4">
<div v-if="os !== 'MacOS'" class="mt-4 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Native decorations</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Native decorations</h2>
<p class="m-0 mt-1">Use system window frame (app restart required).</p>
</div>
<Toggle id="native-decorations" v-model="settings.native_decorations" />
</div>
<div class="mt-6 flex items-center justify-between">
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Minimize launcher</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Minimize launcher</h2>
<p class="m-0 mt-1">Minimize the launcher when a Minecraft process starts.</p>
</div>
<Toggle id="minimize-launcher" v-model="settings.hide_on_process_start" />
</div>
<div class="mt-6 flex items-center justify-between">
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Default landing page</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Default landing page</h2>
<p class="m-0 mt-1">Change the page to which the launcher opens on.</p>
</div>
<Combobox
id="opening-page"
v-model="settings.default_page"
name="Opening page dropdown"
class="max-w-40"
class="w-40"
:options="['Home', 'Library'].map((v) => ({ value: v, label: v }))"
:display-value="settings.default_page ?? 'Select an option'"
/>
</div>
<div class="mt-6 flex items-center justify-between">
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Jump back into worlds</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Jump back into worlds</h2>
<p class="m-0 mt-1">Includes recent worlds in the "Jump back in" section on the Home page.</p>
</div>
<Toggle
@@ -113,9 +113,9 @@ watch(
/>
</div>
<div class="mt-6 flex items-center justify-between">
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Toggle sidebar</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Toggle sidebar</h2>
<p class="m-0 mt-1">Enables the ability to toggle the sidebar.</p>
</div>
<Toggle
@@ -52,135 +52,127 @@ watch(
<template>
<div>
<div class="flex flex-col gap-6">
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-1">
<h3 class="m-0 text-lg font-semibold text-contrast">Fullscreen</h3>
<p class="m-0 leading-tight">
Overwrites the options.txt file to start in full screen when launched.
</p>
</div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Window size</h2>
<Toggle id="fullscreen" v-model="settings.force_fullscreen" />
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Fullscreen</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
Overwrites the options.txt file to start in full screen when launched.
</p>
</div>
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-1">
<h3 class="m-0 text-lg font-semibold text-contrast">Width</h3>
<p class="m-0 leading-tight">The width of the game window when launched.</p>
</div>
<StyledInput
id="width"
v-model="settings.game_resolution[0]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter width..."
/>
</div>
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-1">
<h3 class="m-0 text-lg font-semibold text-contrast">Height</h3>
<p class="m-0 leading-tight">The height of the game window when launched.</p>
</div>
<StyledInput
id="height"
v-model="settings.game_resolution[1]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter height..."
/>
</div>
<Toggle id="fullscreen" v-model="settings.force_fullscreen" />
</div>
<hr class="my-6 bg-button-border border-none h-[1px]" />
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2.5">
<h2 class="m-0 text-lg font-semibold text-contrast">Memory allocated</h2>
<Slider
id="max-memory"
v-model="settings.memory.maximum"
:min="512"
:max="maxMemory"
:step="64"
:snap-points="snapPoints"
:snap-range="512"
unit="MB"
/>
<p class="m-0 mt-1 leading-tight">The memory allocated to each instance when it is ran.</p>
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Width</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The width of the game window when launched.
</p>
</div>
<div class="flex flex-col gap-2.5">
<h2 class="m-0 text-lg font-semibold text-contrast">Java arguments</h2>
<StyledInput
id="java-args"
v-model="settings.launchArgs"
autocomplete="off"
type="text"
placeholder="Enter java arguments..."
wrapper-class="w-full"
/>
</div>
<div class="flex flex-col gap-2.5">
<h2 class="m-0 text-lg font-semibold text-contrast">Environmental variables</h2>
<StyledInput
id="env-vars"
v-model="settings.envVars"
autocomplete="off"
type="text"
placeholder="Enter environmental variables..."
wrapper-class="w-full"
/>
</div>
<StyledInput
id="width"
v-model="settings.game_resolution[0]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter width..."
/>
</div>
<hr class="my-6 bg-button-border border-none h-[1px]" />
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2.5">
<h3 class="m-0 text-lg font-semibold text-contrast">Pre launch hook</h3>
<StyledInput
id="pre-launch"
v-model="settings.hooks.pre_launch"
autocomplete="off"
type="text"
placeholder="Enter pre-launch command..."
wrapper-class="w-full"
/>
<p class="m-0 leading-tight">Ran before the instance is launched.</p>
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Height</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The height of the game window when launched.
</p>
</div>
<div class="flex flex-col gap-2.5">
<h3 class="m-0 text-lg font-semibold text-contrast">Wrapper hook</h3>
<StyledInput
id="wrapper"
v-model="settings.hooks.wrapper"
autocomplete="off"
type="text"
placeholder="Enter wrapper command..."
wrapper-class="w-full"
/>
<p class="m-0 leading-tight">Wrapper command for launching Minecraft.</p>
</div>
<div class="flex flex-col gap-2.5">
<h3 class="m-0 text-lg font-semibold text-contrast">Post exit hook</h3>
<StyledInput
id="post-exit"
v-model="settings.hooks.post_exit"
autocomplete="off"
type="text"
placeholder="Enter post-exit command..."
wrapper-class="w-full"
/>
<p class="m-0 leading-tight">Ran after the game closes.</p>
</div>
<StyledInput
id="height"
v-model="settings.game_resolution[1]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter height..."
/>
</div>
<hr class="mt-4 bg-button-border border-none h-[1px]" />
<h2 class="mt-4 m-0 text-lg font-extrabold text-contrast">Memory allocated</h2>
<p class="m-0 mt-1 leading-tight">The memory allocated to each instance when it is ran.</p>
<Slider
id="max-memory"
v-model="settings.memory.maximum"
:min="512"
:max="maxMemory"
:step="64"
:snap-points="snapPoints"
:snap-range="512"
unit="MB"
/>
<h2 class="mt-4 mb-2 text-lg font-extrabold text-contrast">Java arguments</h2>
<StyledInput
id="java-args"
v-model="settings.launchArgs"
autocomplete="off"
type="text"
placeholder="Enter java arguments..."
wrapper-class="w-full"
/>
<h2 class="mt-4 mb-2 text-lg font-extrabold text-contrast">Environmental variables</h2>
<StyledInput
id="env-vars"
v-model="settings.envVars"
autocomplete="off"
type="text"
placeholder="Enter environmental variables..."
wrapper-class="w-full"
/>
<hr class="mt-4 bg-button-border border-none h-[1px]" />
<h2 class="mt-4 m-0 text-lg font-extrabold text-contrast">Hooks</h2>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Pre launch</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">Ran before the instance is launched.</p>
<StyledInput
id="pre-launch"
v-model="settings.hooks.pre_launch"
autocomplete="off"
type="text"
placeholder="Enter pre-launch command..."
wrapper-class="w-full"
/>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Wrapper</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
Wrapper command for launching Minecraft.
</p>
<StyledInput
id="wrapper"
v-model="settings.hooks.wrapper"
autocomplete="off"
type="text"
placeholder="Enter wrapper command..."
wrapper-class="w-full"
/>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Post exit</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">Ran after the game closes.</p>
<StyledInput
id="post-exit"
v-model="settings.hooks.post_exit"
autocomplete="off"
type="text"
placeholder="Enter post-exit command..."
wrapper-class="w-full"
/>
</div>
</template>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ButtonStyled, Toggle } from '@modrinth/ui'
import { Toggle } from '@modrinth/ui'
import { ref, watch } from 'vue'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
@@ -25,28 +25,17 @@ watch(
)
</script>
<template>
<div class="flex flex-col gap-2.5 min-w-[600px]">
<div v-for="option in options" :key="option" class="flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast capitalize">
{{ option.replaceAll('_', ' ') }}
</h2>
</div>
<div class="flex items-center gap-2">
<ButtonStyled type="transparent">
<button
:disabled="themeStore.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
@click="setFeatureFlag(option, DEFAULT_FEATURE_FLAGS[option])"
>
Reset to default
</button>
</ButtonStyled>
<Toggle
id="advanced-rendering"
:model-value="themeStore.getFeatureFlag(option)"
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
/>
</div>
<div v-for="option in options" :key="option" class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast capitalize">
{{ option.replaceAll('_', ' ') }}
</h2>
</div>
<Toggle
id="advanced-rendering"
:model-value="themeStore.getFeatureFlag(option)"
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
/>
</div>
</template>
@@ -21,21 +21,15 @@ async function updateJavaVersion(version) {
}
</script>
<template>
<div class="flex flex-col gap-6">
<div
v-for="(javaVersion, index) in [25, 21, 17, 8]"
:key="`java-${javaVersion}`"
class="flex flex-col gap-2.5"
>
<h2 class="m-0 text-lg font-semibold text-contrast" :class="{ 'mt-4': index !== 0 }">
Java {{ javaVersion }} location
</h2>
<JavaSelector
:id="'java-selector-' + javaVersion"
v-model="javaVersions[javaVersion]"
:version="javaVersion"
@update:model-value="updateJavaVersion"
/>
</div>
<div v-for="(javaVersion, index) in [25, 21, 17, 8]" :key="`java-${javaVersion}`">
<h2 class="m-0 text-lg font-extrabold text-contrast" :class="{ 'mt-4': index !== 0 }">
Java {{ javaVersion }} location
</h2>
<JavaSelector
:id="'java-selector-' + javaVersion"
v-model="javaVersions[javaVersion]"
:version="javaVersion"
@update:model-value="updateJavaVersion"
/>
</div>
</template>
@@ -43,7 +43,7 @@ async function onLocaleChange(newLocale: string) {
</script>
<template>
<h2 class="m-0 text-lg font-semibold text-contrast">Language</h2>
<h2 class="m-0 text-lg font-extrabold text-contrast">Language</h2>
<Admonition type="warning" class="mt-2 mb-4">
{{ formatMessage(languageSelectorMessages.languageWarning, { platform }) }}
@@ -25,8 +25,8 @@ watch(
<template>
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Personalized ads</h2>
<p class="m-0 mt-1 text-sm">
<h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2>
<p class="m-0 text-sm">
Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
option, you opt out and ads will no longer be shown based on your interests.
</p>
@@ -36,8 +36,8 @@ watch(
<div class="mt-4 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Telemetry</h2>
<p class="m-0 mt-1 text-sm">
<h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2>
<p class="m-0 text-sm">
Modrinth collects anonymized analytics and usage data to improve our user experience and
customize your experience. By disabling this option, you opt out and your data will no
longer be collected.
@@ -48,8 +48,8 @@ watch(
<div class="mt-4 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Discord RPC</h2>
<p class="m-0 mt-1 text-sm">
<h2 class="m-0 text-lg font-extrabold text-contrast">Discord RPC</h2>
<p class="m-0 text-sm">
Manages the Discord Rich Presence integration. Disabling this will cause 'Modrinth' to no
longer show up as a game or app you are using on your Discord profile.
</p>
@@ -62,77 +62,67 @@ async function findLauncherDir() {
</script>
<template>
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2.5">
<h2 class="m-0 text-lg font-semibold text-contrast">App directory</h2>
<StyledInput
id="appDir"
v-model="settings.custom_dir"
:icon="BoxIcon"
type="text"
wrapper-class="w-full"
>
<template #right>
<Button class="ml-1.5" @click="findLauncherDir">
<FolderSearchIcon />
</Button>
</template>
</StyledInput>
<p class="m-0 leading-tight text-secondary">
The directory where the launcher stores all of its files. Changes will be applied after
restarting the launcher.
</p>
</div>
<h2 class="m-0 text-lg font-extrabold text-contrast">App directory</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The directory where the launcher stores all of its files. Changes will be applied after
restarting the launcher.
</p>
<div class="flex flex-col gap-2.5">
<ConfirmModalWrapper
ref="purgeCacheConfirmModal"
title="Are you sure you want to purge the cache?"
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
:has-to-type="false"
proceed-label="Purge cache"
:show-ad-on-close="false"
@proceed="purgeCache"
/>
<h2 class="m-0 text-lg font-semibold text-contrast">App cache</h2>
<button id="purge-cache" class="btn min-w-max" @click="$refs.purgeCacheConfirmModal.show()">
<TrashIcon />
Purge cache
</button>
<p class="m-0 leading-tight text-secondary">
The Modrinth app stores a cache of data to speed up loading. This can be purged to force the
app to reload data. This may slow down the app temporarily.
</p>
</div>
<div class="flex flex-col gap-2.5">
<h2 class="m-0 text-lg font-semibold text-contrast mt-4">Maximum concurrent downloads</h2>
<Slider
id="max-downloads"
v-model="settings.max_concurrent_downloads"
:min="1"
:max="10"
:step="1"
/>
<p class="m-0 leading-tight text-secondary">
The maximum amount of files the launcher can download at the same time. Set this to a lower
value if you have a poor internet connection. (app restart required to take effect)
</p>
</div>
<div class="flex flex-col gap-2.5">
<h2 class="mt-0 m-0 text-lg font-semibold text-contrast">Maximum concurrent writes</h2>
<Slider
id="max-writes"
v-model="settings.max_concurrent_writes"
:min="1"
:max="50"
:step="1"
/>
<p class="m-0 leading-tight text-secondary">
The maximum amount of files the launcher can write to the disk at once. Set this to a lower
value if you are frequently getting I/O errors. (app restart required to take effect)
</p>
</div>
<div class="m-1 my-2">
<StyledInput
id="appDir"
v-model="settings.custom_dir"
:icon="BoxIcon"
type="text"
wrapper-class="w-full"
>
<template #right>
<Button class="r-btn" @click="findLauncherDir">
<FolderSearchIcon />
</Button>
</template>
</StyledInput>
</div>
<div>
<ConfirmModalWrapper
ref="purgeCacheConfirmModal"
title="Are you sure you want to purge the cache?"
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
:has-to-type="false"
proceed-label="Purge cache"
:show-ad-on-close="false"
@proceed="purgeCache"
/>
<h2 class="m-0 text-lg font-extrabold text-contrast">App cache</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The Modrinth app stores a cache of data to speed up loading. This can be purged to force the
app to reload data. This may slow down the app temporarily.
</p>
</div>
<button id="purge-cache" class="btn min-w-max" @click="$refs.purgeCacheConfirmModal.show()">
<TrashIcon />
Purge cache
</button>
<h2 class="m-0 text-lg font-extrabold text-contrast mt-4">Maximum concurrent downloads</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The maximum amount of files the launcher can download at the same time. Set this to a lower
value if you have a poor internet connection. (app restart required to take effect)
</p>
<Slider
id="max-downloads"
v-model="settings.max_concurrent_downloads"
:min="1"
:max="10"
:step="1"
/>
<h2 class="mt-4 m-0 text-lg font-extrabold text-contrast">Maximum concurrent writes</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The maximum amount of files the launcher can write to the disk at once. Set this to a lower
value if you are frequently getting I/O errors. (app restart required to take effect)
</p>
<Slider id="max-writes" v-model="settings.max_concurrent_writes" :min="1" :max="50" :step="1" />
</template>
@@ -79,7 +79,7 @@ watch([() => props.recentInstances, () => showWorlds.value], async () => {
})
})
await populateJumpBackIn()
populateJumpBackIn()
.catch(() => {
console.error('Failed to populate jump back in')
})
@@ -189,22 +189,6 @@ const messages = defineMessages({
id: 'instance.worlds.linked_server',
defaultMessage: 'Managed by server project',
},
incompatibleVersion: {
id: 'app.world.world-item.incompatible-version',
defaultMessage: 'Incompatible version {version}',
},
playersOnline: {
id: 'app.world.world-item.players-online',
defaultMessage: '{count} online',
},
offline: {
id: 'app.world.world-item.offline',
defaultMessage: 'Offline',
},
notPlayedYet: {
id: 'app.world.world-item.not-played-yet',
defaultMessage: 'Not played yet',
},
})
</script>
<template>
@@ -259,17 +243,13 @@ const messages = defineMessages({
>
<template v-if="refreshing">
<SpinnerIcon aria-hidden="true" class="animate-spin shrink-0" />
{{ formatMessage(commonMessages.loadingLabel) }}
Loading...
</template>
<template v-else-if="serverStatus">
<template v-if="serverIncompatible">
<IssuesIcon class="shrink-0 text-orange" aria-hidden="true" />
<span class="text-orange">
{{
formatMessage(messages.incompatibleVersion, {
version: serverStatus.version?.name,
})
}}
Incompatible version {{ serverStatus.version?.name }}
</span>
</template>
<template v-else>
@@ -285,11 +265,8 @@ const messages = defineMessages({
/>
<Tooltip :disabled="!hasPlayersTooltip">
<span :class="{ 'cursor-help': hasPlayersTooltip }">
{{
formatMessage(messages.playersOnline, {
count: formatNumber(serverStatus.players?.online ?? 0),
})
}}
{{ formatNumber(serverStatus.players?.online) }}
online
</span>
<template #popper>
<div class="flex flex-col gap-1">
@@ -303,7 +280,7 @@ const messages = defineMessages({
</template>
<template v-else>
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" />
{{ formatMessage(messages.offline) }}
Offline
</template>
</div>
</div>
@@ -322,7 +299,7 @@ const messages = defineMessages({
})
}}
</template>
<template v-else> {{ formatMessage(messages.notPlayedYet) }} </template>
<template v-else> Not played yet </template>
</div>
<template v-if="instancePath">
@@ -5,11 +5,12 @@ import {
commonMessages,
defineMessages,
injectNotificationManager,
NewModal,
useVIntl,
} from '@modrinth/ui'
import { ref } from 'vue'
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
@@ -25,10 +26,10 @@ const props = defineProps<{
instance: GameInstance
}>()
const modal = ref<InstanceType<typeof NewModal>>()
const modal = ref()
const name = ref('')
const address = ref('')
const name = ref()
const address = ref()
const resourcePack = ref<ServerPackStatus>('enabled')
async function addServer(play: boolean) {
@@ -59,11 +60,11 @@ function show() {
name.value = ''
address.value = ''
resourcePack.value = 'enabled'
modal.value?.show()
modal.value.show()
}
function hide() {
modal.value?.hide()
modal.value.hide()
}
const messages = defineMessages({
@@ -84,33 +85,37 @@ const messages = defineMessages({
defineExpose({ show, hide })
</script>
<template>
<NewModal ref="modal" :header="formatMessage(messages.title)" width="500px" max-width="500px">
<ModalWrapper ref="modal">
<template #title>
<span class="flex items-center gap-2 text-lg font-semibold text-primary">
<InstanceModalTitlePrefix :instance="instance" />
<span class="font-extrabold text-contrast">{{ formatMessage(messages.title) }}</span>
</span>
</template>
<ServerModalBody
v-model:name="name"
v-model:address="address"
v-model:resource-pack="resourcePack"
/>
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button :disabled="!address" @click="addServer(false)">
<PlusIcon />
{{ formatMessage(messages.addServer) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!address" @click="addServer(true)">
<PlayIcon />
{{ formatMessage(messages.addAndPlay) }}
</button>
</ButtonStyled>
</div>
</template>
</NewModal>
<div class="flex gap-2 mt-4">
<ButtonStyled color="brand">
<button :disabled="!address" @click="addServer(true)">
<PlayIcon />
{{ formatMessage(messages.addAndPlay) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button :disabled="!address" @click="addServer(false)">
<PlusIcon />
{{ formatMessage(messages.addServer) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
</div>
</ModalWrapper>
</template>
@@ -5,11 +5,11 @@ import {
commonMessages,
defineMessage,
injectNotificationManager,
NewModal,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types'
@@ -32,7 +32,7 @@ const props = defineProps<{
instance: GameInstance
}>()
const modal = ref<InstanceType<typeof NewModal>>()
const modal = ref()
const name = ref<string>('')
const address = ref<string>('')
@@ -81,11 +81,11 @@ function show(server: ServerWorld) {
index.value = server.index
displayStatus.value = server.display_status
hideFromHome.value = server.display_status === 'hidden'
modal.value?.show()
modal.value.show()
}
function hide() {
modal.value?.hide()
modal.value.hide()
}
defineExpose({ show })
@@ -96,28 +96,29 @@ const titleMessage = defineMessage({
})
</script>
<template>
<NewModal ref="modal" :header="formatMessage(titleMessage)" width="500px" max-width="500px">
<ModalWrapper ref="modal">
<template #title>
<span class="font-extrabold text-lg text-contrast">{{ formatMessage(titleMessage) }}</span>
</template>
<ServerModalBody
v-model:name="name"
v-model:address="address"
v-model:resource-pack="resourcePack"
/>
<HideFromHomeOption v-model="hideFromHome" class="mt-3" />
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button :disabled="!address" @click="saveServer">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</button>
</ButtonStyled>
</div>
</template>
</NewModal>
<div class="flex gap-2 mt-4">
<ButtonStyled color="brand">
<button :disabled="!address" @click="saveServer">
<SaveIcon />
{{ formatMessage(commonMessages.saveChangesButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="hide()">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
</div>
</ModalWrapper>
</template>
@@ -49,40 +49,34 @@ const messages = defineMessages({
id: 'instance.server-modal.placeholder-name',
defaultMessage: 'Minecraft Server',
},
placeholderAddress: {
id: 'app.world.server-modal.placeholder-address',
defaultMessage: 'example.modrinth.gg',
},
selectAnOption: {
id: 'app.world.server-modal.select-an-option',
defaultMessage: 'Select an option',
},
})
defineExpose({ resourcePackOptions })
</script>
<template>
<div class="space-y-4 w-full">
<label class="flex flex-col gap-2">
<span class="font-semibold text-contrast">{{ formatMessage(messages.name) }}</span>
<StyledInput
v-model="name"
:placeholder="formatMessage(messages.placeholderName)"
autocomplete="off"
wrapper-class="w-full"
/>
</label>
<label class="flex flex-col gap-2">
<span class="font-semibold text-contrast">{{ formatMessage(messages.address) }}</span>
<StyledInput
v-model="address"
:placeholder="formatMessage(messages.placeholderAddress)"
autocomplete="off"
wrapper-class="w-full"
/>
</label>
<label class="flex flex-col gap-2">
<span class="font-semibold text-contrast">{{ formatMessage(messages.resourcePack) }}</span>
<div class="w-[450px]">
<h2 class="text-lg font-extrabold text-contrast mt-0 mb-1">
{{ formatMessage(messages.name) }}
</h2>
<StyledInput
v-model="name"
:placeholder="formatMessage(messages.placeholderName)"
autocomplete="off"
wrapper-class="w-full"
/>
<h2 class="text-lg font-extrabold text-contrast mt-3 mb-1">
{{ formatMessage(messages.address) }}
</h2>
<StyledInput
v-model="address"
placeholder="example.modrinth.gg"
autocomplete="off"
wrapper-class="w-full"
/>
<h2 class="text-lg font-extrabold text-contrast mt-3 mb-1">
{{ formatMessage(messages.resourcePack) }}
</h2>
<div>
<Combobox
v-model="resourcePack"
:options="
@@ -95,9 +89,9 @@ defineExpose({ resourcePackOptions })
:display-value="
resourcePack
? formatMessage(resourcePackOptionMessages[resourcePack])
: formatMessage(messages.selectAnOption)
: 'Select an option'
"
/>
</label>
</div>
</div>
</template>
@@ -1,99 +0,0 @@
import { createConsoleState } from '@modrinth/ui'
import { clear_log_buffer, get_live_log_buffer, get_logs } from '@/helpers/logs'
type ConsoleState = ReturnType<typeof createConsoleState>
interface LogEntry {
filename: string
name?: string
log_type: string
stdout?: string
age?: number
live?: boolean
}
interface InstanceConsoleEntry {
liveConsole: ConsoleState
historicalConsole: ConsoleState
historicalCache: Map<string, string>
logList: LogEntry[] | null
}
const instances = new Map<string, InstanceConsoleEntry>()
function getOrCreate(profilePathId: string): InstanceConsoleEntry {
let entry = instances.get(profilePathId)
if (entry) return entry
entry = {
liveConsole: createConsoleState(),
historicalConsole: createConsoleState(),
historicalCache: new Map(),
logList: null,
}
instances.set(profilePathId, entry)
return entry
}
async function hydrate(profilePathId: string): Promise<void> {
const entry = getOrCreate(profilePathId)
if (entry.liveConsole.output.value.length > 0) return
const buffer = await get_live_log_buffer(profilePathId)
if (buffer) {
entry.liveConsole.addLegacyLog(buffer)
}
}
async function getHistoricalLogs(profilePathId: string, instancePath: string): Promise<LogEntry[]> {
const entry = getOrCreate(profilePathId)
if (entry.logList) return entry.logList
const logs: LogEntry[] = await get_logs(instancePath, false)
entry.logList = logs
for (const log of logs) {
if (log.stdout && log.stdout !== '') {
entry.historicalCache.set(log.filename, log.stdout)
}
}
return logs
}
function getHistoricalContent(profilePathId: string, filename: string): string | undefined {
return instances.get(profilePathId)?.historicalCache.get(filename)
}
function invalidate(profilePathId: string): void {
const entry = instances.get(profilePathId)
if (!entry) return
entry.historicalCache.clear()
entry.logList = null
}
async function clearLive(profilePathId: string): Promise<void> {
const entry = getOrCreate(profilePathId)
entry.liveConsole.clear()
await clear_log_buffer(profilePathId).catch(() => {})
}
async function destroy(profilePathId: string): Promise<void> {
instances.delete(profilePathId)
await clear_log_buffer(profilePathId).catch(() => {})
}
export function useInstanceConsole(profilePathId: string) {
const entry = getOrCreate(profilePathId)
return {
liveConsole: entry.liveConsole,
historicalConsole: entry.historicalConsole,
hydrate: () => hydrate(profilePathId),
getHistoricalLogs: (instancePath: string) => getHistoricalLogs(profilePathId, instancePath),
getHistoricalContent: (filename: string) => getHistoricalContent(profilePathId, filename),
invalidate: () => invalidate(profilePathId),
clearLive: () => clearLive(profilePathId),
destroy: () => destroy(profilePathId),
}
}
-18
View File
@@ -1,18 +0,0 @@
const trimTrailingSlash = (url: string) => url.replace(/\/$/, '')
const siteUrl = trimTrailingSlash(import.meta.env.MODRINTH_URL || 'https://modrinth.com')
const labrinthBaseUrl = trimTrailingSlash(
import.meta.env.MODRINTH_API_BASE_URL || 'https://api.modrinth.com',
)
const archonBaseUrl = trimTrailingSlash(
import.meta.env.MODRINTH_ARCHON_BASE_URL || 'https://archon.modrinth.com',
)
export const config = {
siteUrl,
stripePublishableKey:
import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY ||
'pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b',
labrinthBaseUrl,
archonBaseUrl,
}
@@ -1,35 +0,0 @@
import { OverlayScrollbars, type PartialOptions } from 'overlayscrollbars'
import type { ObjectDirective } from 'vue'
const defaultOverlayScrollbarsOptions = Object.freeze<PartialOptions>({
scrollbars: {
theme: 'os-theme-dark',
autoHide: 'leave',
autoHideSuspend: true,
},
})
const mergeOptions = (options: PartialOptions = {}): PartialOptions => ({
...defaultOverlayScrollbarsOptions,
...options,
scrollbars: {
...defaultOverlayScrollbarsOptions.scrollbars,
...(options.scrollbars ?? {}),
},
})
export const overlayScrollbarsDirective: ObjectDirective<HTMLElement, PartialOptions | undefined> =
{
mounted(el, binding) {
OverlayScrollbars(el, mergeOptions(binding.value))
},
updated(el, binding) {
if (binding.value === binding.oldValue) return
const instance = OverlayScrollbars(el)
instance?.options(mergeOptions(binding.value))
},
unmounted(el) {
const instance = OverlayScrollbars(el)
instance?.destroy()
},
}
-20
View File
@@ -97,23 +97,3 @@ export async function warning_listener(callback) {
export async function friend_listener(callback) {
return await listen('friend', (event) => callback(event.payload))
}
/// Payload for the 'log' event
/*
LogPayload {
profile_path_id: string,
type: "log4j" | "legacy",
// log4j fields (when type === "log4j"):
timestamp_millis?: number,
logger_name?: string,
level?: string,
thread_name?: string,
message?: string,
throwable?: string,
// legacy fields (when type === "legacy"):
message?: string,
}
*/
export async function log_listener(callback) {
return await listen('log', (event) => callback(event.payload))
}
-10
View File
@@ -63,13 +63,3 @@ export async function delete_logs(profilePath) {
export async function get_latest_log_cursor(profilePath, cursor) {
return await invoke('plugin:logs|logs_get_latest_log_cursor', { profilePath, cursor })
}
/// Get all buffered live log lines for a profile from the Rust ring buffer
export async function get_live_log_buffer(profilePath) {
return await invoke('plugin:logs|logs_get_live_log_buffer', { profilePath })
}
/// Clear the live log buffer for a profile on the Rust side
export async function clear_log_buffer(profilePath) {
return await invoke('plugin:logs|logs_clear_live_log_buffer', { profilePath })
}
-6
View File
@@ -30,8 +30,6 @@ export type ServerWorld = BaseWorld & {
index: number
address: string
pack_status: ServerPackStatus
project_id?: string
content_kind?: string
}
export type World = SingleplayerWorld | ServerWorld
@@ -142,16 +140,12 @@ export async function add_server_to_profile(
name: string,
address: string,
packStatus: ServerPackStatus,
projectId?: string,
contentKind?: string,
): Promise<number> {
return await invoke('plugin:worlds|add_server_to_profile', {
path,
name,
address,
packStatus,
projectId,
contentKind,
})
}
+51 -117
View File
@@ -5,41 +5,8 @@
"app.auth-servers.unreachable.header": {
"message": "تعذر الوصول إلى خوادم المصادقة"
},
"app.browse.add-server-to-instance": {
"message": "أضف الخادم للنموذج"
},
"app.browse.add-servers-to-instance": {
"message": "أضف الخوادم لنموذجك"
},
"app.browse.add-to-instance": {
"message": "أضف للنموذج"
},
"app.browse.add-to-instance-name": {
"message": "أضف للنموذج {instanceName}"
},
"app.browse.added": {
"message": "مضاف"
},
"app.browse.already-added": {
"message": "مضاف فعلا"
},
"app.browse.discover-content": {
"message": "استكشف محتوى"
},
"app.browse.discover-servers": {
"message": "استكشف خوادم"
},
"app.browse.hide-added-servers": {
"message": "إخفاء الخوادم المضافة"
},
"app.browse.hide-installed-content": {
"message": "إخفاء المحتوى المضاف"
},
"app.browse.install-content-to-instance": {
"message": "تثبيت محتوى لنموذجك"
},
"app.export-modal.description-placeholder": {
"message": "أدخل وصف التعديل..."
"message": "ادخل وصف التعديل..."
},
"app.export-modal.export-button": {
"message": "تصدير"
@@ -63,37 +30,49 @@
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "سيتم حذف جميع البيانات الخاصة لنموذجك نهائيًا، بما في ذلك عوالمك والتكوينات وكل المحتوى المثبت."
"message": "سيتم حذف جميع البيانات الخاصة بمثيلك نهائيًا، بما في ذلك عوالمك والتكوينات وكل المحتوى المثبت."
},
"app.instance.confirm-delete.admonition-header": {
"message": "لا يمكن التراجع عن هذا الإجراء"
},
"app.instance.confirm-delete.delete-button": {
"message": "حذف النموذج"
"message": "حذف المثيل"
},
"app.instance.confirm-delete.header": {
"message": "حذف النموذج"
"message": "حذف المثيل"
},
"app.instance.modpack-already-installed.body": {
"message": "حُزْمَة التعديل هذه مثبته فعلًا في نموذج <bold>{instanceName}</bold>. هل انت متأكد بإرادة نسخه؟"
"app.instance.modpack-already-installed.admonition-body": {
"message": "تم تثبيت حزمة التعديل هذه بالفعل في مثيل \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "إنشاء"
"app.instance.modpack-already-installed.admonition-header": {
"message": "حزمة تعديل مكررة"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "إنشاء على أي حال"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "انتقل إلى المثال"
},
"app.instance.modpack-already-installed.header": {
"message": "حُزْمَة التعديل مثبتة بالفعل"
},
"app.instance.modpack-already-installed.instance": {
"message": "النموذج"
"message": "تم تثبيت حزمة التعديل بالفعل"
},
"app.instance.mods.content-type-project": {
"message": "مشروع"
},
"app.instance.mods.copy-link": {
"message": "نسخ الرابط"
},
"app.instance.mods.installing": {
"message": "جاري التثبيت..."
},
"app.instance.mods.modpack-fallback": {
"message": "حزمة التعديل"
},
"app.instance.mods.project-was-added": {
"message": "تمت إضافة \"{name}\""
"message": "تمت إضافة \"{name}\"."
},
"app.instance.mods.projects-were-added": {
"message": "تمت إضافة {count} مشروع"
"message": "تمت إضافة {count} من المشاريع"
},
"app.instance.mods.share-text": {
"message": "تحقق من المشاريع التي أستخدمها في حزمة التعديل الخاص بي!"
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "مشاركة محتوى حزمة التعديل"
},
"app.instance.mods.show-file": {
"message": "عرض الملف"
},
"app.instance.mods.successfully-uploaded": {
"message": "تم الرفع بنجاح"
},
"app.instance.worlds.add-server": {
"message": "أضف خادم"
"app.instance.mods.unknown-version": {
"message": "غير معروف"
},
"app.instance.worlds.browse-servers": {
"message": "تصفح الخوادم"
},
"app.instance.worlds.delete-world-description": {
"message": "سيتم حذف '{name}' **نهائيا**, و لن هناك أي طريقة لاسترداده."
},
"app.instance.worlds.delete-world-title": {
"message": "هل أنت متيقِّن من رغبتك بحذف هذا العالم نهائيا؟"
},
"app.instance.worlds.filter-modded": {
"message": "معدّل"
},
"app.instance.worlds.filter-offline": {
"message": "مفصول"
},
"app.instance.worlds.filter-online": {
"message": "يعمل"
},
"app.instance.worlds.filter-vanilla": {
"message": "الأصلي"
},
"app.instance.worlds.no-worlds-description": {
"message": "أضف خادما أو تصفح لكي تبدأ"
},
"app.instance.worlds.no-worlds-heading": {
"message": "لم تتم إضافة أي من الخوادم أو العوالم"
},
"app.instance.worlds.remove-server-description": {
"message": "ستتم إزالة '{name}' من قائمتك, إضافة لما داخل اللعبة, و لن يكون هناك أي طريقة لاسترداده."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "ستتم إزالة '{name}' ({address}) من قائمتك, إضافة لما داخل اللعبة, و لن يكون هناك أي طريقة لاسترداده."
},
"app.instance.worlds.remove-server-title": {
"message": "هل أنت متيقِّن من رغبتك في إزالة {name}؟"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "ابحث بين ال{count} عوالم..."
},
"app.instance.worlds.this-server": {
"message": "هذا الخادم"
"app.instance.mods.updating": {
"message": "جارٍ التحديث..."
},
"app.modal.install-to-play.content-required": {
"message": "المحتوى مطلوب"
@@ -159,7 +102,7 @@
"message": "تثبيت"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# تعديل} other {# تعديلات}}"
"message": ""
},
"app.modal.install-to-play.required-modpack": {
"message": "حزمة التعديل مطلوبة"
@@ -168,10 +111,10 @@
"message": "يتطلب هذا الخادم تعديلات للعب. انقر فوق \"تثبيت\" لإعداد الملفات المطلوبة من Modrinth، ثم قم بتشغيله مباشرة إلى الخادم."
},
"app.modal.install-to-play.shared-instance": {
"message": "النماذج المشتركة"
"message": "حُزْمَة مشاركة"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "نماذج الخادم مشتركة"
"message": "حُزْمَة خادم مشاركة"
},
"app.modal.install-to-play.view-contents": {
"message": "عرض المحتويات"
@@ -183,7 +126,7 @@
"message": "يلزم التحديث"
},
"app.modal.update-to-play.update-required-description": {
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى آخر اصدار لتشغيل اللعبة."
"message": "هناك تحديث لازم للعب بـ {name}. الرجاء التحديث إلى آخر اصدار لتشغيل اللعبة."
},
"app.settings.developer-mode-enabled": {
"message": "تم تفعيل وضع المطوّر."
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "خيارات النسخة الافتراضية"
},
"app.settings.tabs.feature-flags": {
"message": "أعلام الميزات"
},
"app.settings.tabs.java-installations": {
"message": "تثبيتات جافا"
},
@@ -251,21 +197,6 @@
"app.update.reload-to-update": {
"message": "أعد التحميل لتثبيت التحديث"
},
"app.world.server-modal.select-an-option": {
"message": "حدد خيارا"
},
"app.world.world-item.incompatible-version": {
"message": "إصدار غير مطابق ({version})"
},
"app.world.world-item.not-played-yet": {
"message": "لم يتم اللعب به"
},
"app.world.world-item.offline": {
"message": "مفصول"
},
"app.world.world-item.players-online": {
"message": "{count} متصل حاليا"
},
"friends.action.add-friend": {
"message": "إضافة صديق"
},
@@ -365,12 +296,6 @@
"instance.edit-world.title": {
"message": "تعديل العالم"
},
"instance.files.adding-files": {
"message": "إضافة ملفات ({completed}\\{total})"
},
"instance.files.save-as": {
"message": "حفظ ك..."
},
"instance.server-modal.address": {
"message": "العنوان"
},
@@ -408,7 +333,7 @@
"message": "نسخة النسخة"
},
"instance.settings.tabs.general.duplicate-instance.description": {
"message": "ينشئ نسخة من هذا النموذج شاملا عوالمه, تعديلاتها النصيه, إلخ..."
"message": "إنشاء."
},
"instance.settings.tabs.general.edit-icon": {
"message": "تعديل الأيقونة"
@@ -542,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "عدم العرض في الصفحة الرئيسية"
},
"instance.worlds.filter.available": {
"message": "متاح"
},
"instance.worlds.game_already_open": {
"message": "النسخة مفتوحة بالفعل"
},
@@ -566,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "تشغيل النسخة"
},
"instance.worlds.type.server": {
"message": "الخادم"
},
"instance.worlds.type.singleplayer": {
"message": "لعب فردي"
},
"instance.worlds.view_instance": {
"message": "عرض النسخة"
},
@@ -588,7 +522,7 @@
"message": "يقدمها الخادم"
},
"search.filter.locked.server-environment.title": {
"message": "يمكن إضافة التعديلات **المحليه** فقط إلى نموذج الخادم"
"message": "يمكن إضافة التعديلات من جانب العميل فقط إلى مثيل الخادم"
},
"search.filter.locked.server-game-version.title": {
"message": "يتم توفير نسخة اللعبة من قبل الخادم"
@@ -47,6 +47,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Výchozí možnosti instance"
},
"app.settings.tabs.feature-flags": {
"message": "Vlajky funkcí"
},
"app.settings.tabs.java-installations": {
"message": "Instalace Javy"
},
@@ -347,6 +350,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Nezobrazuje na domovské stránce"
},
"instance.worlds.filter.available": {
"message": "Dostupné"
},
"instance.worlds.game_already_open": {
"message": "Instance je již otevřená"
},
@@ -368,6 +374,12 @@
"instance.worlds.play_instance": {
"message": "Hrát instanci"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Hra pro jednoho hráče"
},
"instance.worlds.view_instance": {
"message": "Zobrazit instanci"
},
+30 -27
View File
@@ -5,27 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Kan ikke nå autentificeringsservere"
},
"app.browse.add-to-instance-name": {
"message": "Tilføjet til {instanceName}"
},
"app.browse.added": {
"message": "Tilføjet"
},
"app.browse.already-added": {
"message": "Allerede tilføjet"
},
"app.browse.discover-content": {
"message": "Opdag indhold"
},
"app.browse.discover-servers": {
"message": "Opdag servere"
},
"app.browse.hide-added-servers": {
"message": "Gem tilføjet servere"
},
"app.browse.hide-installed-content": {
"message": "Gem installeret indhold"
},
"app.export-modal.description-placeholder": {
"message": "Indtast modpack beskrivelse..."
},
@@ -62,15 +41,18 @@
"app.instance.confirm-delete.header": {
"message": "Slet instance"
},
"app.instance.modpack-already-installed.body": {
"message": "Denne modpack er allerede installeret i <bold>{instanceName}</bold> instancen. Er du sikker på du vil duplikere den?"
},
"app.instance.modpack-already-installed.create": {
"message": "Opret"
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
"app.instance.mods.copy-link": {
"message": "Kopier link"
},
"app.instance.mods.installing": {
"message": "Installer..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" blev tilføjet"
},
@@ -83,9 +65,18 @@
"app.instance.mods.share-title": {
"message": "Deler modpack indhold"
},
"app.instance.mods.show-file": {
"message": "Vis filer"
},
"app.instance.mods.successfully-uploaded": {
"message": "Uploadet"
},
"app.instance.mods.unknown-version": {
"message": "Ukendt"
},
"app.instance.mods.updating": {
"message": "Opdatere..."
},
"app.modal.install-to-play.content-required": {
"message": "Indhold krævet"
},
@@ -134,6 +125,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Standardindstillinger for instans"
},
"app.settings.tabs.feature-flags": {
"message": "Funktionsflags"
},
"app.settings.tabs.java-installations": {
"message": "Javainstallationer"
},
@@ -458,6 +452,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Vis ikke på forsiden"
},
"instance.worlds.filter.available": {
"message": "Tilgængelig"
},
"instance.worlds.game_already_open": {
"message": "Instance allerede åben"
},
@@ -482,6 +479,12 @@
"instance.worlds.play_instance": {
"message": "Spil instance"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Singleplayer"
},
"instance.worlds.view_instance": {
"message": "Se instance"
},
+38 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Authentifizierungsserver sind nicht erreichbar"
},
"app.browse.add-server-to-instance": {
"message": "Server zu Instanz hinzufügen"
},
"app.browse.add-servers-to-instance": {
"message": "Server zu deiner Instanz hinzufügen"
},
"app.browse.add-to-instance": {
"message": "Zu Instanz hinzufügen"
},
"app.browse.add-to-instance-name": {
"message": "Zu {instanceName} hinzufügen"
},
"app.browse.added": {
"message": "Hinzugefügt"
},
"app.browse.already-added": {
"message": "Bereits hinzugefügt"
},
"app.browse.discover-content": {
"message": "Inhalte entdecken"
},
"app.browse.discover-servers": {
"message": "Server entdecken"
},
"app.browse.hide-added-servers": {
"message": "Hinzugefügte Server ausblenden"
},
"app.browse.hide-installed-content": {
"message": "Installierte Inhalte ausblenden"
},
"app.browse.install-content-to-instance": {
"message": "Inhalt in Instanz installieren"
},
"app.export-modal.description-placeholder": {
"message": "Modpaketbeschreibung eingeben..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Instanz löschen"
},
"app.instance.modpack-already-installed.body": {
"message": "Dieses Modpack ist bereits in der Instanz <bold>{instanceName}</bold> installiert. Möchtest du es wirklich duplizieren?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Dieses Modpack ist bereits in der Instanz {instanceName} installiert."
},
"app.instance.modpack-already-installed.create": {
"message": "Erstellen"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Modpack duplizieren"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Trotzdem erstellen"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Gehe zu Instanz"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack bereits installiert"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instanz"
},
"app.instance.mods.content-type-project": {
"message": "Projekt"
},
"app.instance.mods.copy-link": {
"message": "Link kopieren"
},
"app.instance.mods.installing": {
"message": "Wird installiert..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpaket"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" wurde hinzugefügt"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Modpaketinhalte teilen"
},
"app.instance.mods.show-file": {
"message": "Datei anzeigen"
},
"app.instance.mods.successfully-uploaded": {
"message": "Erfolgreich hochgeladen"
},
"app.instance.worlds.add-server": {
"message": "Server hinzufügen"
"app.instance.mods.unknown-version": {
"message": "Unbekannt"
},
"app.instance.worlds.browse-servers": {
"message": "Server durchsuchen"
},
"app.instance.worlds.delete-world-description": {
"message": "„{name}“ wird **endgültig gelöscht**, und es gibt keine Möglichkeit, sie wiederherzustellen."
},
"app.instance.worlds.delete-world-title": {
"message": "Möchtest du diese Welt wirklich endgültig löschen?"
},
"app.instance.worlds.filter-modded": {
"message": "Modifiziert"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Füge einen Server hinzu oder durchsuche, um loszulegen"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.remove-server-description": {
"message": "„{name}“ wird aus deiner Liste entfernt, auch im Spiel, und es gibt keine Möglichkeit, ihn wiederherzustellen."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "„{name}“ ({address}) wird aus deiner Liste entfernt, inklusive im Spiel, und es gibt keine Möglichkeit, ihn wiederherzustellen."
},
"app.instance.worlds.remove-server-title": {
"message": "Möchtest du {name} wirklich entfernen?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Durchsuche {count} Welten..."
},
"app.instance.worlds.this-server": {
"message": "dieser Server"
"app.instance.mods.updating": {
"message": "Wird aktualisiert..."
},
"app.modal.install-to-play.content-required": {
"message": "Inhalte benötigt"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Standard Instanz-Einstellungen"
},
"app.settings.tabs.feature-flags": {
"message": "Funktionsflaggen"
},
"app.settings.tabs.java-installations": {
"message": "Java Installationen"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Lade neu um Aktualisierung zu installieren"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Wählen eine Option"
},
"app.world.world-item.incompatible-version": {
"message": "Inkompatible Version {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Noch nicht gespielt"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Freund hinzufügen"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Welt bearbeiten"
},
"instance.files.adding-files": {
"message": "Dateien werden hinzugefügt ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Speichern unter..."
},
"instance.server-modal.address": {
"message": "Adresse"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Nicht auf Startseite anzeigen"
},
"instance.worlds.filter.available": {
"message": "Verfügbar"
},
"instance.worlds.game_already_open": {
"message": "Instanz ist bereits geöffnet"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Instanz Spielen"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Einzelspieler"
},
"instance.worlds.view_instance": {
"message": "Instanz anzeigen"
},
+38 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Authentifizierungsserver sind nicht erreichbar"
},
"app.browse.add-server-to-instance": {
"message": "Server zur Instanz hinzufügen"
},
"app.browse.add-servers-to-instance": {
"message": "Server zu deiner Instanz hinzufügen"
},
"app.browse.add-to-instance": {
"message": "Zur Instanz hinzufügen"
},
"app.browse.add-to-instance-name": {
"message": "Zu {instanceName} hinzufügen"
},
"app.browse.added": {
"message": "Hinzugefügt"
},
"app.browse.already-added": {
"message": "Bereits hinzugefügt"
},
"app.browse.discover-content": {
"message": "Inhalte entdecken"
},
"app.browse.discover-servers": {
"message": "Server entdecken"
},
"app.browse.hide-added-servers": {
"message": "Hinzugefügte Server ausblenden"
},
"app.browse.hide-installed-content": {
"message": "Installierte Inhalte ausblenden"
},
"app.browse.install-content-to-instance": {
"message": "Inhalt in Instanz installieren"
},
"app.export-modal.description-placeholder": {
"message": "Beschreibung des Modpacks eingeben..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Instanz löschen"
},
"app.instance.modpack-already-installed.body": {
"message": "Dieses Modpack ist bereits in der Instanz <bold>{instanceName}</bold> installiert. Möchtest du es wirklich duplizieren?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Dieses Modpack ist bereits in der Instanz {instanceName} installiert."
},
"app.instance.modpack-already-installed.create": {
"message": "Erstellen"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Modpack duplizieren"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Trotzdem erstellen"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Gehe zu Instanz"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack bereits installiert"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instanz"
},
"app.instance.mods.content-type-project": {
"message": "Projekt"
},
"app.instance.mods.copy-link": {
"message": "Link kopieren"
},
"app.instance.mods.installing": {
"message": "Wird installiert..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" wurde hinzugefügt"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Modpackinhalte teilen"
},
"app.instance.mods.show-file": {
"message": "Datei anzeigen"
},
"app.instance.mods.successfully-uploaded": {
"message": "Erfolgreich hochgeladen"
},
"app.instance.worlds.add-server": {
"message": "Server hinzufügen"
"app.instance.mods.unknown-version": {
"message": "Unbekannt"
},
"app.instance.worlds.browse-servers": {
"message": "Server durchsuchen"
},
"app.instance.worlds.delete-world-description": {
"message": "„{name}“ wird **endgültig gelöscht**, und es gibt keine Möglichkeit, sie wiederherzustellen."
},
"app.instance.worlds.delete-world-title": {
"message": "Möchtest du diese Welt wirklich endgültig löschen?"
},
"app.instance.worlds.filter-modded": {
"message": "Modifiziert"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Füge einen Server hinzu oder durchsuche, um loszulegen"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.remove-server-description": {
"message": "„{name}“ wird aus deiner Liste entfernt, auch im Spiel, und es gibt keine Möglichkeit, ihn wiederherzustellen."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "„{name}“ ({address}) wird aus deiner Liste entfernt, inklusive im Spiel, und es gibt keine Möglichkeit, ihn wiederherzustellen."
},
"app.instance.worlds.remove-server-title": {
"message": "Möchtest du {name} wirklich entfernen?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Durchsuche {count} Welten..."
},
"app.instance.worlds.this-server": {
"message": "dieser Server"
"app.instance.mods.updating": {
"message": "Wird aktualisiert..."
},
"app.modal.install-to-play.content-required": {
"message": "Inhalte benötigt"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Standard Instanz-Einstellungen"
},
"app.settings.tabs.feature-flags": {
"message": "Funktionsflags"
},
"app.settings.tabs.java-installations": {
"message": "Java-Installationen"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Neu laden, um das Update zu installieren"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Wählen eine Option"
},
"app.world.world-item.incompatible-version": {
"message": "Inkompatible Version {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Noch nicht gespielt"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Freund hinzufügen"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Welt bearbeiten"
},
"instance.files.adding-files": {
"message": "Dateien werden hinzugefügt ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Speichern unter..."
},
"instance.server-modal.address": {
"message": "Adresse"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Nicht auf der Startseite anzeigen"
},
"instance.worlds.filter.available": {
"message": "Verfügbar"
},
"instance.worlds.game_already_open": {
"message": "Instanz ist bereits geöffnet"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Instanz spielen"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Einzelspieler"
},
"instance.worlds.view_instance": {
"message": "Instanz anzeigen"
},
+38 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Cannot reach authentication servers"
},
"app.browse.add-server-to-instance": {
"message": "Add server to instance"
},
"app.browse.add-servers-to-instance": {
"message": "Add servers to your instance"
},
"app.browse.add-to-instance": {
"message": "Add to instance"
},
"app.browse.add-to-instance-name": {
"message": "Add to {instanceName}"
},
"app.browse.added": {
"message": "Added"
},
"app.browse.already-added": {
"message": "Already added"
},
"app.browse.discover-content": {
"message": "Discover content"
},
"app.browse.discover-servers": {
"message": "Discover servers"
},
"app.browse.hide-added-servers": {
"message": "Hide added servers"
},
"app.browse.hide-installed-content": {
"message": "Hide installed content"
},
"app.browse.install-content-to-instance": {
"message": "Install content to instance"
},
"app.export-modal.description-placeholder": {
"message": "Enter modpack description..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Delete instance"
},
"app.instance.modpack-already-installed.body": {
"message": "This modpack is already installed in the <bold>{instanceName}</bold> instance. Are you sure you want to duplicate it?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "This modpack is already installed in the \"{instanceName}\" instance."
},
"app.instance.modpack-already-installed.create": {
"message": "Create"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Duplicate modpack"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Create anyway"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Go to instance"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack already installed"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.content-type-project": {
"message": "project"
},
"app.instance.mods.copy-link": {
"message": "Copy link"
},
"app.instance.mods.installing": {
"message": "Installing..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" was added"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Sharing modpack content"
},
"app.instance.mods.show-file": {
"message": "Show file"
},
"app.instance.mods.successfully-uploaded": {
"message": "Successfully uploaded"
},
"app.instance.worlds.add-server": {
"message": "Add server"
"app.instance.mods.unknown-version": {
"message": "Unknown"
},
"app.instance.worlds.browse-servers": {
"message": "Browse servers"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' will be **permanently deleted**, and there will be no way to recover it."
},
"app.instance.worlds.delete-world-title": {
"message": "Are you sure you want to permanently delete this world?"
},
"app.instance.worlds.filter-modded": {
"message": "Modded"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Add a server or browse to get started"
},
"app.instance.worlds.no-worlds-heading": {
"message": "No servers or worlds added"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' will be removed from your list, including in-game, and there will be no way to recover it."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) will be removed from your list, including in-game, and there will be no way to recover it."
},
"app.instance.worlds.remove-server-title": {
"message": "Are you sure you want to remove {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Search {count} worlds..."
},
"app.instance.worlds.this-server": {
"message": "this server"
"app.instance.mods.updating": {
"message": "Updating..."
},
"app.modal.install-to-play.content-required": {
"message": "Content required"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Default instance options"
},
"app.settings.tabs.feature-flags": {
"message": "Feature flags"
},
"app.settings.tabs.java-installations": {
"message": "Java installations"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Reload to install update"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Select an option"
},
"app.world.world-item.incompatible-version": {
"message": "Incompatible version {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Not played yet"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Add a friend"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Edit world"
},
"instance.files.adding-files": {
"message": "Adding files ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Save as..."
},
"instance.server-modal.address": {
"message": "Address"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Don't show on Home"
},
"instance.worlds.filter.available": {
"message": "Available"
},
"instance.worlds.game_already_open": {
"message": "Instance is already open"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Play instance"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Singleplayer"
},
"instance.worlds.view_instance": {
"message": "View instance"
},
+50 -119
View File
@@ -5,41 +5,8 @@
"app.auth-servers.unreachable.header": {
"message": "No se puede acceder a los servidores de autenticación"
},
"app.browse.add-server-to-instance": {
"message": "Añadir servidor a instancia"
},
"app.browse.add-servers-to-instance": {
"message": "Añade servidores a tu instancia"
},
"app.browse.add-to-instance": {
"message": "Añadir a instancia"
},
"app.browse.add-to-instance-name": {
"message": "Añadir a {instanceName}"
},
"app.browse.added": {
"message": "Añadido"
},
"app.browse.already-added": {
"message": "Ya se ha añadido"
},
"app.browse.discover-content": {
"message": "Descubrir contenido"
},
"app.browse.discover-servers": {
"message": "Descubrir servidores"
},
"app.browse.hide-added-servers": {
"message": "Ocultar servidores añadidos"
},
"app.browse.hide-installed-content": {
"message": "Ocultar contenido instalado"
},
"app.browse.install-content-to-instance": {
"message": "Instalar contenido a la instancia"
},
"app.export-modal.description-placeholder": {
"message": "Introduce la descripción del modpack..."
"message": "Escribe la descripción del modpack..."
},
"app.export-modal.export-button": {
"message": "Exportar"
@@ -54,16 +21,16 @@
"message": "Nombre del modpack"
},
"app.export-modal.select-files-label": {
"message": "Selecciona archivos y carpetas para incluirlos en el pack"
"message": "Seleccione archivos y carpetas para incluir en el paquete"
},
"app.export-modal.version-number-label": {
"message": "Número de la versión"
"message": "Número de versión"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
"message": "Todos los datos de su instancia se eliminarán permanentemente, incluidos sus mundos, configuraciones y todo el contenido instalado."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Esta acción no se puede deshacer"
@@ -74,80 +41,56 @@
"app.instance.confirm-delete.header": {
"message": "Eliminar instancia"
},
"app.instance.modpack-already-installed.body": {
"message": "El modpack ya está instalado en la instancia <bold>{instanceName}</bold>. ¿Estás seguro de querer duplicarlo?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Este modpack ya está instalado en la instancia \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Crear"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Modpack duplicado"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Crea de todos modos"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Ir a la instancia"
},
"app.instance.modpack-already-installed.header": {
"message": "Este modpack ya está instalado"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instancia"
"message": "Modpack ya está instalado"
},
"app.instance.mods.content-type-project": {
"message": "proyecto"
},
"app.instance.mods.copy-link": {
"message": "Copiar enlace"
},
"app.instance.mods.installing": {
"message": "Instalando..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "Se añadió \"{name}\""
"message": "Se ha añadido \"{name}\""
},
"app.instance.mods.projects-were-added": {
"message": "Se añadieron {count} proyectos"
"message": "Se han añadido {count} proyectos"
},
"app.instance.mods.share-text": {
"message": Mira a los proyectos que estoy usando en mi modpack!"
"message": Echa un vistazo a los proyectos que utilizo en mi modpack!"
},
"app.instance.mods.share-title": {
"message": "Compartiendo contenido del modpack"
"message": "Compartir contenido del modpack"
},
"app.instance.mods.show-file": {
"message": "Mostrar archivo"
},
"app.instance.mods.successfully-uploaded": {
"message": "Cargado correctamente"
"message": "Se ha subido correctamente"
},
"app.instance.worlds.add-server": {
"message": "Añadir servidor"
"app.instance.mods.unknown-version": {
"message": "Desconocido"
},
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-description": {
"message": "\"{name}\" se **eliminará permanentemente** y no habrá forma de recuperarlo."
},
"app.instance.worlds.delete-world-title": {
"message": "¿Estás seguro de que quieres eliminar este mundo de forma permanente?"
},
"app.instance.worlds.filter-modded": {
"message": "Con mods"
},
"app.instance.worlds.filter-offline": {
"message": "Sin conexión"
},
"app.instance.worlds.filter-online": {
"message": "En línea"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Añadir un servidor o explora para comenzar"
},
"app.instance.worlds.no-worlds-heading": {
"message": "No hay servidores ni mundos añadidos"
},
"app.instance.worlds.remove-server-description": {
"message": "\"{name}\" se eliminará de tu lista, también dentro del juego, y no podrás recuperarlo."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "\"{name}\" ({address}) se eliminará de tu lista, también dentro del juego, y no podrás recuperarlo."
},
"app.instance.worlds.remove-server-title": {
"message": "¿Estás seguro de que quieres eliminar {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar en {count} mundos..."
},
"app.instance.worlds.this-server": {
"message": "este servidor"
"app.instance.mods.updating": {
"message": "Actualizando..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenido requerido"
@@ -165,7 +108,7 @@
"message": "Modpack requerido"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Este servidor requiere mods para poder jugar. Haz clic en Instalar para configurar los archivos requeridos desde Modrinth, después se ejecutará para entrar directamente al servidor."
"message": "Este servidor requiere mods para poder jugar. Haz click en Instalar para configurar los archivos requeridos desde Modrinth, después se ejecutará para entrar directamente al servidor."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instancia compartida"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Opciones predeterminadas de la instancia"
},
"app.settings.tabs.feature-flags": {
"message": "Funciones experimentales"
},
"app.settings.tabs.java-installations": {
"message": "Instalaciones de Java"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Recarga para instalar la actualización"
},
"app.world.server-modal.placeholder-address": {
"message": "ejemplo.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Selecciona una opción"
},
"app.world.world-item.incompatible-version": {
"message": "Versión {version} incompatible"
},
"app.world.world-item.not-played-yet": {
"message": "Aún no se ha jugado"
},
"app.world.world-item.offline": {
"message": "Desconectado"
},
"app.world.world-item.players-online": {
"message": "{count} en línea"
},
"friends.action.add-friend": {
"message": "Añadir un amigo"
},
@@ -285,7 +213,7 @@
"message": "¡Podría ser distinto a su nombre de usuario de Minecraft!"
},
"friends.add-friend.username.placeholder": {
"message": "Introduce el nombre de usuario de Modrinth..."
"message": "Ingresa tu nombre de usuario de Modrinth..."
},
"friends.add-friend.username.title": {
"message": "¿Cuál es el nombre de usuario de Modrinth de tu amigo?"
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Editar mundo"
},
"instance.files.adding-files": {
"message": "Añadiendo archivos ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Guardar como..."
},
"instance.server-modal.address": {
"message": "Dirección IP"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "No mostrar en la página de inicio"
},
"instance.worlds.filter.available": {
"message": "Disponible"
},
"instance.worlds.game_already_open": {
"message": "La instancia ya está abierta"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Iniciar instancia"
},
"instance.worlds.type.server": {
"message": "Servidor"
},
"instance.worlds.type.singleplayer": {
"message": "Un jugador"
},
"instance.worlds.view_instance": {
"message": "Ver instancia"
},
+38 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "No se puede conectar con los servidores de autenticación"
},
"app.browse.add-server-to-instance": {
"message": "Añadir servidor a la instancia"
},
"app.browse.add-servers-to-instance": {
"message": "Añadir servidor a tu instancia"
},
"app.browse.add-to-instance": {
"message": "Añadir a la instancia"
},
"app.browse.add-to-instance-name": {
"message": "Añadir a {instanceName}"
},
"app.browse.added": {
"message": "Añadido"
},
"app.browse.already-added": {
"message": "Ya está añadido"
},
"app.browse.discover-content": {
"message": "Descubrir contenido"
},
"app.browse.discover-servers": {
"message": "Descubrir servidores"
},
"app.browse.hide-added-servers": {
"message": "Ocultar servidores añadidos"
},
"app.browse.hide-installed-content": {
"message": "Ocultar contenido instalado"
},
"app.browse.install-content-to-instance": {
"message": "Instalar contenido a una instancia"
},
"app.export-modal.description-placeholder": {
"message": "Escribe la descripción del modpack..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Eliminar instancia"
},
"app.instance.modpack-already-installed.body": {
"message": "Este modpack ya está instalado en la instancia <bold>{instanceName}</bold>. ¿Estás seguro qué quieres duplicarlo?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Este modpack ya está instalado en la instancia \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Crear"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Modpack duplicado"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Crea de todos modos"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Ir a la instancia"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack ya está instalado"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instancia"
},
"app.instance.mods.content-type-project": {
"message": "proyecto"
},
"app.instance.mods.copy-link": {
"message": "Copiar enlace"
},
"app.instance.mods.installing": {
"message": "Instalando..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "Se ha añadido \"{name}\""
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Compartir contenido del modpack"
},
"app.instance.mods.show-file": {
"message": "Mostrar archivo"
},
"app.instance.mods.successfully-uploaded": {
"message": "Se ha subido correctamente"
},
"app.instance.worlds.add-server": {
"message": "Añadir servidor"
"app.instance.mods.unknown-version": {
"message": "Desconocido"
},
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' será **permanentemente eliminado**, y habrá forma de recuperarlo."
},
"app.instance.worlds.delete-world-title": {
"message": "¿Estás seguro de qué quieres eliminar permanentemente este mundo?"
},
"app.instance.worlds.filter-modded": {
"message": "Modeado"
},
"app.instance.worlds.filter-offline": {
"message": "Sin conexión"
},
"app.instance.worlds.filter-online": {
"message": "En línea"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Añadir un servidor o explora para comenzar"
},
"app.instance.worlds.no-worlds-heading": {
"message": "No hay servidores o mundos añadidos"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' será eliminado de tu lista, incluyendo dentro del juego, y no habrá forma de recuperarlo."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) será eliminado de tu lista, incluyendo dentro del juego, y no habrá forma de recuperarlo."
},
"app.instance.worlds.remove-server-title": {
"message": "¿Estás seguro de qué quieres eliminar {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar {count} mundos..."
},
"app.instance.worlds.this-server": {
"message": "este servidor"
"app.instance.mods.updating": {
"message": "Actualizando..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenido obligatorio"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Opciones predeterminadas de la instancia"
},
"app.settings.tabs.feature-flags": {
"message": "Funciones experimentales"
},
"app.settings.tabs.java-installations": {
"message": "Instalaciones de Java"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Recarga para instalar la actualización"
},
"app.world.server-modal.placeholder-address": {
"message": "ejemplo.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Selecciona una opción"
},
"app.world.world-item.incompatible-version": {
"message": "Versión {version} incompatible"
},
"app.world.world-item.not-played-yet": {
"message": "No se ha jugado aún"
},
"app.world.world-item.offline": {
"message": "Desconectado"
},
"app.world.world-item.players-online": {
"message": "{count} en línea"
},
"friends.action.add-friend": {
"message": "Agrega a un amigo"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Editar mundo"
},
"instance.files.adding-files": {
"message": "Añadiendo archivos ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Guardar como..."
},
"instance.server-modal.address": {
"message": "Dirección IP"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "No mostrar en Inicio"
},
"instance.worlds.filter.available": {
"message": "Disponible"
},
"instance.worlds.game_already_open": {
"message": "La instancia ya está abierta"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Jugar instancia"
},
"instance.worlds.type.server": {
"message": "Servidor"
},
"instance.worlds.type.singleplayer": {
"message": "Un jugador"
},
"instance.worlds.view_instance": {
"message": "Ver instancia"
},
@@ -71,6 +71,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Oletusinstanssin asetukset"
},
"app.settings.tabs.feature-flags": {
"message": "Ominaisuuskytkimet"
},
"app.settings.tabs.java-installations": {
"message": "Java asennukset"
},
@@ -395,6 +398,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Älä näytä koti näkymässä"
},
"instance.worlds.filter.available": {
"message": "Saatavilla"
},
"instance.worlds.game_already_open": {
"message": "Instanssi on jo avattu"
},
@@ -419,6 +425,12 @@
"instance.worlds.play_instance": {
"message": "Pelaa instanssia"
},
"instance.worlds.type.server": {
"message": "Palvelin"
},
"instance.worlds.type.singleplayer": {
"message": "Yksinpeli"
},
"instance.worlds.view_instance": {
"message": "Näytä instanssi"
},
+25 -88
View File
@@ -5,110 +5,38 @@
"app.auth-servers.unreachable.header": {
"message": "Hindi maabot ang mga authentication server"
},
"app.browse.add-server-to-instance": {
"message": "Idagdag ang server sa instansiya"
},
"app.browse.add-servers-to-instance": {
"message": "Idagdag ang mga server sa iyong instansiya"
},
"app.browse.add-to-instance": {
"message": "Idagdag sa instansiya"
},
"app.browse.add-to-instance-name": {
"message": "Idagdag sa {instanceName}"
},
"app.browse.added": {
"message": "Dinagdag"
},
"app.browse.already-added": {
"message": "Nadagdag na"
},
"app.browse.discover-content": {
"message": "Tumuklas ng kontento"
},
"app.browse.discover-servers": {
"message": "Tumuklas ng mga server"
},
"app.browse.hide-added-servers": {
"message": "Taguin ang mga nadagdag na server"
},
"app.browse.hide-installed-content": {
"message": "Taguin ang mga na-install na kontento"
},
"app.browse.install-content-to-instance": {
"message": "I-install ang kontento sa instansiya"
},
"app.export-modal.description-placeholder": {
"message": "Ilagay ang paglalarawan ng modpack..."
},
"app.export-modal.export-button": {
"message": "Iluwas"
},
"app.export-modal.header": {
"message": "Iluwas ang modpack"
},
"app.export-modal.modpack-name-label": {
"message": "Pangalan ng Modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Pangalan ng modpack"
},
"app.export-modal.select-files-label": {
"message": "Pumili ng mga talaksan at folder na isasali sa pack"
},
"app.export-modal.version-number-label": {
"message": "Numero ng bersiyon"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Ang lahat ng data ng iyong instansiya ay tuluyang mawawala, kabilang na ang iyong mga mundo, kumpigurasyon, at lahat ng na-install na kontento."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Hindi mababawi ang aksiyong ito"
},
"app.instance.confirm-delete.delete-button": {
"message": "Tanggalin ang instansiya"
},
"app.instance.confirm-delete.header": {
"message": "Tanggalin ang instansiya"
},
"app.instance.modpack-already-installed.create": {
"message": "Ilikha"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instansiya"
},
"app.instance.mods.content-type-project": {
"message": "proyekto"
},
"app.instance.mods.copy-link": {
"message": "Kopyahin ang link"
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "Ang \"{name}\" ay nadagdag"
},
"app.instance.mods.projects-were-added": {
"message": "{count} proyekto ang nadagdag"
},
"app.instance.mods.share-title": {
"message": "Binabahagi ang kontento ng modpack"
},
"app.instance.mods.successfully-uploaded": {
"message": "Matagumpay na na-upload"
},
"app.instance.worlds.add-server": {
"message": "Magdagdag ng server"
},
"app.instance.worlds.filter-modded": {
"message": "Modded"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
"app.instance.mods.unknown-version": {
"message": "Hindi kilala"
},
"app.modal.install-to-play.content-required": {
"message": "Nangangailangan ng kontento"
@@ -147,7 +75,7 @@
"message": "Kailangang mag-update upang malaro ang {name}. Mangyaring mag-update sa pinakabagong bersiyon upang ma-launch ang laro."
},
"app.settings.developer-mode-enabled": {
"message": "Binuksan ang moda ng nagdi-develop."
"message": "Nakabukas ang moda ng nagdidibelop."
},
"app.settings.downloading": {
"message": "Dina-download ang v{version}"
@@ -158,6 +86,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Mga pagpipilian sa default na instansiya"
},
"app.settings.tabs.feature-flags": {
"message": "Mga hudyat ng tampok"
},
"app.settings.tabs.java-installations": {
"message": "Mga instalasyon ng Java"
},
@@ -212,9 +143,6 @@
"app.update.reload-to-update": {
"message": "Handang ma-install ang update"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"friends.action.add-friend": {
"message": "Magdagdag ng kaibigan"
},
@@ -285,10 +213,10 @@
"message": "Idagdag ang server"
},
"instance.add-server.resource-pack.disabled": {
"message": "Tanggihan"
"message": "Hindi pinahihintulotan"
},
"instance.add-server.resource-pack.enabled": {
"message": "Payagan"
"message": "Pinahihintulotan"
},
"instance.add-server.resource-pack.prompt": {
"message": "Magpahintulot"
@@ -300,7 +228,7 @@
"message": "Baguhin ang server"
},
"instance.edit-world.hide-from-home": {
"message": "Itago sa pahina ng Tahanan"
"message": "Huwag ipakita sa Home na pahina"
},
"instance.edit-world.name": {
"message": "Pangalan"
@@ -483,7 +411,10 @@
"message": "Kopyahin ang adres"
},
"instance.worlds.dont_show_on_home": {
"message": "Huwag ipakita sa Tahanan"
"message": "Huwag ipakita sa Home"
},
"instance.worlds.filter.available": {
"message": "Magagamit"
},
"instance.worlds.game_already_open": {
"message": "Bukas naman ang instansiya"
@@ -509,6 +440,12 @@
"instance.worlds.play_instance": {
"message": "Laruin ang instansiya"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Pang-isahang laro"
},
"instance.worlds.view_instance": {
"message": "Tingnan ang instansiya"
},
+40 -109
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Impossible de contacter les serveurs d'authentification"
},
"app.browse.add-server-to-instance": {
"message": "Ajouter le serveur à l'instance"
},
"app.browse.add-servers-to-instance": {
"message": "Ajouter des serveurs à votre instance"
},
"app.browse.add-to-instance": {
"message": "Ajouter à l'instance"
},
"app.browse.add-to-instance-name": {
"message": "Ajouter à {instanceName}"
},
"app.browse.added": {
"message": "Ajouté"
},
"app.browse.already-added": {
"message": "Déjà ajouté"
},
"app.browse.discover-content": {
"message": "Découvrir du contenu"
},
"app.browse.discover-servers": {
"message": "Découvrir des serveurs"
},
"app.browse.hide-added-servers": {
"message": "Masquer les serveurs ajoutés"
},
"app.browse.hide-installed-content": {
"message": "Masquer le contenu installé"
},
"app.browse.install-content-to-instance": {
"message": "Installer du contenu à l'instance"
},
"app.export-modal.description-placeholder": {
"message": "Saisir la description du modpack..."
},
@@ -63,7 +30,7 @@
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Toutes les données pour votre instance seront supprimées à jamais, y compris vos mondes, vos configurations, et le contenu installé."
"message": "Toutes les données pour votre instance seront supprimée à jamais, y comprit vos mondes, configurations, et contenu supprimé."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Cette action est irréversible"
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Supprimer l'instance"
},
"app.instance.modpack-already-installed.body": {
"message": "Ce modpack est déjà installé sur l'instance <bold>{instanceName}</bold>. Êtes-vous sûr.e de vouloir le dupliquer ?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Ce modpack est déjà installé dans linstance « {instanceName} »."
},
"app.instance.modpack-already-installed.create": {
"message": "Créer"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Dupliquer le modpack"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Créer quand même"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Aller à linstance"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack déjà installé"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.content-type-project": {
"message": "projet"
},
"app.instance.mods.copy-link": {
"message": "Copier le lien"
},
"app.instance.mods.installing": {
"message": "Installation..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "« {name} » a été ajouté"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Partagez le contenu de votre modpack"
},
"app.instance.mods.show-file": {
"message": "Montrer le fichier"
},
"app.instance.mods.successfully-uploaded": {
"message": "Mis en ligne avec succès"
},
"app.instance.worlds.add-server": {
"message": "Ajouter le serveur"
"app.instance.mods.unknown-version": {
"message": "Inconnu"
},
"app.instance.worlds.browse-servers": {
"message": "Parcourir des serveurs"
},
"app.instance.worlds.delete-world-description": {
"message": "« {name} » sera supprimé **pour toujours**, et il sera impossible de le récupérer."
},
"app.instance.worlds.delete-world-title": {
"message": "Êtes-vous sûr.e de vouloir supprimer ce monde pour toujours ?"
},
"app.instance.worlds.filter-modded": {
"message": "Moddé"
},
"app.instance.worlds.filter-offline": {
"message": "Hors ligne"
},
"app.instance.worlds.filter-online": {
"message": "En ligne"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Ajouter un serveur ou parcourir pour commencer"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Aucun serveur ou monde ajoutés"
},
"app.instance.worlds.remove-server-description": {
"message": "« {name} » sera retiré de votre liste, y compris en jeu, et il sera impossible de le récupérer."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "« {name} » ({address}) sera retiré de votre liste, y compris en jeu, et il sera impossible de le récupérer."
},
"app.instance.worlds.remove-server-title": {
"message": "Êtes-vous sûr.e de vouloir retirer {name} ?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Rechercher {count} mondes..."
},
"app.instance.worlds.this-server": {
"message": "ce serveur"
"app.instance.mods.updating": {
"message": "Mise à jour..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenu requis"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Options dinstance par défaut"
},
"app.settings.tabs.feature-flags": {
"message": "Menu de fonctionnalité"
},
"app.settings.tabs.java-installations": {
"message": "Installations de Java"
},
@@ -210,7 +156,7 @@
"message": "Gestion des ressources"
},
"app.update-popup.body": {
"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."
"message": "Modrinth App v{version} est prête à être installée ! Rechargez pour mettre à jour maintenant, ou automatiquement quand vous fermez Modrinth App."
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} a finie d'être téléchargée. Rechargez pour mettre à jour maintenant, ou automatiquement quand vous fermez Modrinth App."
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Relancez l'application pour installer la mise à jour"
},
"app.world.server-modal.placeholder-address": {
"message": "exemple.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Sélectionner une option"
},
"app.world.world-item.incompatible-version": {
"message": "Version {version} incompatible"
},
"app.world.world-item.not-played-yet": {
"message": "Pas encore joué"
},
"app.world.world-item.offline": {
"message": "Hors ligne"
},
"app.world.world-item.players-online": {
"message": "{count, plural, =0 {Personne en ligne} one {# est en ligne} other {# sont en ligne}}"
},
"friends.action.add-friend": {
"message": "Ajouter un ami"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Modifier le monde"
},
"instance.files.adding-files": {
"message": "Ajout de fichiers ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Sauvegarder en tant que..."
},
"instance.server-modal.address": {
"message": "Adresse"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Ne pas montrer dans l'Accueil"
},
"instance.worlds.filter.available": {
"message": "Disponible"
},
"instance.worlds.game_already_open": {
"message": "L'instance est déjà ouverte"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Jouer à l'instance"
},
"instance.worlds.type.server": {
"message": "Serveur"
},
"instance.worlds.type.singleplayer": {
"message": "Solo"
},
"instance.worlds.view_instance": {
"message": "Voir l'instance"
},
+28 -112
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "לא ניתן לגשת לשרתי האימות"
},
"app.browse.add-server-to-instance": {
"message": "הוסף שרת להתקנה"
},
"app.browse.add-servers-to-instance": {
"message": "הוסף שרתים להתקנה שלך"
},
"app.browse.add-to-instance": {
"message": "הוספה להתקנה"
},
"app.browse.add-to-instance-name": {
"message": "הוסף אל {instanceName}"
},
"app.browse.added": {
"message": "נוסף"
},
"app.browse.already-added": {
"message": "כבר נוסף"
},
"app.browse.discover-content": {
"message": "גלה תוכן"
},
"app.browse.discover-servers": {
"message": "גלה שרתים"
},
"app.browse.hide-added-servers": {
"message": "הסתר שרתים שנוספו"
},
"app.browse.hide-installed-content": {
"message": "הסתר תוכן מותקן"
},
"app.browse.install-content-to-instance": {
"message": "הוספת התוכן להתקנה"
},
"app.export-modal.description-placeholder": {
"message": "הזן את תיאור חבילת המודים..."
},
@@ -74,21 +41,18 @@
"app.instance.confirm-delete.header": {
"message": "מחיקת התקנה"
},
"app.instance.modpack-already-installed.body": {
"message": "חבילת המודים הזו כבר מותקנת בתוך ההתקנה <bold>{instanceName}</bold>. האם אתה בטוח שברצונך לשכפל אותה?"
},
"app.instance.modpack-already-installed.create": {
"message": "צור"
},
"app.instance.modpack-already-installed.header": {
"message": "חבילת המודים הזאת כבר מותקנת"
},
"app.instance.modpack-already-installed.instance": {
"message": "התקנה"
},
"app.instance.mods.content-type-project": {
"message": "פרויקט"
},
"app.instance.mods.copy-link": {
"message": "העתק קישור"
},
"app.instance.mods.installing": {
"message": "מתקין..."
},
"app.instance.mods.modpack-fallback": {
"message": "חבילת מודים"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" נוסף"
},
@@ -101,53 +65,17 @@
"app.instance.mods.share-title": {
"message": "משתף את התוכן של חבילת המודים"
},
"app.instance.mods.show-file": {
"message": "מצא קובץ"
},
"app.instance.mods.successfully-uploaded": {
"message": "הועלה בהצלחה"
},
"app.instance.worlds.add-server": {
"message": "הוסף שרת"
"app.instance.mods.unknown-version": {
"message": "לא ידוע"
},
"app.instance.worlds.browse-servers": {
"message": "עיון בשרתים"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' יימחק לצמיתות, ולא תהיה דרך לשחזר אותו."
},
"app.instance.worlds.delete-world-title": {
"message": "האם אתה בטוח שברצונך למחוק לצמיתות את העולם הזה?"
},
"app.instance.worlds.filter-modded": {
"message": "עם מודים"
},
"app.instance.worlds.filter-offline": {
"message": "לא מקוון"
},
"app.instance.worlds.filter-online": {
"message": "מחובר"
},
"app.instance.worlds.filter-vanilla": {
"message": "ונילה"
},
"app.instance.worlds.no-worlds-description": {
"message": "הוספת שרת או עיון כדי להתחיל"
},
"app.instance.worlds.no-worlds-heading": {
"message": "אין שרתים אן עולמות שנוספו"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' יוסר מהרשימה שלך, כולל מתוך המשחק, ולא תהיה דרך לשחזר אותו."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) יוסר מהרשימה שלך, כולל מתוך המשחק, ולא תהיה דרך לשחזר אותו."
},
"app.instance.worlds.remove-server-title": {
"message": "אתה בטוח שאתה רוצה להסיר {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "חיפוש ב-{count} עולמות..."
},
"app.instance.worlds.this-server": {
"message": "השרת הזה"
"app.instance.mods.updating": {
"message": "מעדכן..."
},
"app.modal.install-to-play.content-required": {
"message": "תוכן נדרש"
@@ -197,6 +125,9 @@
"app.settings.tabs.default-instance-options": {
"message": "אפשרויות ברירת מחדל של התקנה"
},
"app.settings.tabs.feature-flags": {
"message": "הגדרות ניסיוניות"
},
"app.settings.tabs.java-installations": {
"message": "התקנות ג'אווה"
},
@@ -251,24 +182,6 @@
"app.update.reload-to-update": {
"message": "צריך לרענן כדי להתקין את העדכון"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "בחר אופציה"
},
"app.world.world-item.incompatible-version": {
"message": "גרסה לא תואמת: {version}"
},
"app.world.world-item.not-played-yet": {
"message": "עדיין לא שוחק"
},
"app.world.world-item.offline": {
"message": "לא מקוון"
},
"app.world.world-item.players-online": {
"message": "{count} מחוברים"
},
"friends.action.add-friend": {
"message": "הוספת חבר"
},
@@ -368,12 +281,6 @@
"instance.edit-world.title": {
"message": "ערוך עולם"
},
"instance.files.adding-files": {
"message": "הוספת קבצים ({completed}/{total})"
},
"instance.files.save-as": {
"message": "שמור כ..."
},
"instance.server-modal.address": {
"message": "כתובת"
},
@@ -545,6 +452,9 @@
"instance.worlds.dont_show_on_home": {
"message": "אל תציג בדף הבית"
},
"instance.worlds.filter.available": {
"message": "זמין"
},
"instance.worlds.game_already_open": {
"message": "התקנה כבר פתוחה"
},
@@ -569,6 +479,12 @@
"instance.worlds.play_instance": {
"message": "שחק בהתקנה"
},
"instance.worlds.type.server": {
"message": "שרת"
},
"instance.worlds.type.singleplayer": {
"message": "שחקן יחיד"
},
"instance.worlds.view_instance": {
"message": "הצג התקנה"
},
+44 -113
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Nem lehet elérni a hitelesítési kiszolgálókat"
},
"app.browse.add-server-to-instance": {
"message": "Szerver hozzáadása a profilhoz"
},
"app.browse.add-servers-to-instance": {
"message": "Szerverek hozzáadása a profilodhoz"
},
"app.browse.add-to-instance": {
"message": "Hozzáadás a profilhoz"
},
"app.browse.add-to-instance-name": {
"message": "Hozzáadás ehhez: {instanceName}"
},
"app.browse.added": {
"message": "Hozzáadva"
},
"app.browse.already-added": {
"message": "Már hozzá van adva"
},
"app.browse.discover-content": {
"message": "Tartalom böngészése"
},
"app.browse.discover-servers": {
"message": "Szerverek böngészése"
},
"app.browse.hide-added-servers": {
"message": "Hozzáadott szerverek elrejtése"
},
"app.browse.hide-installed-content": {
"message": "A telepített tartalmak elrejtése"
},
"app.browse.install-content-to-instance": {
"message": "Tartalom letöltése a profilhoz"
},
"app.export-modal.description-placeholder": {
"message": "Írd be a modcsomag leírását..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Profil törlése"
},
"app.instance.modpack-already-installed.body": {
"message": "Ez a modcsomag már telepítve van a(z) <bold>{instanceName}</bold> profilban. Biztosan duplikálni szeretnéd?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Ez a modcsomag már telepítve van a{instanceName} profilban."
},
"app.instance.modpack-already-installed.create": {
"message": "Létrehozás"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Modcsomag duplikálása"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Létrehozás mindenképpen"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Profil megnyitása"
},
"app.instance.modpack-already-installed.header": {
"message": "A modcsomag már telepítve van"
},
"app.instance.modpack-already-installed.instance": {
"message": "Profil"
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
"app.instance.mods.copy-link": {
"message": "Link másolása"
},
"app.instance.mods.installing": {
"message": "Telepítés..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modcsomag"
},
"app.instance.mods.project-was-added": {
"message": " „{name}” hozzá lett adva"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Modcsomag-tartalmak megosztása"
},
"app.instance.mods.show-file": {
"message": "Fájl megjelenítése"
},
"app.instance.mods.successfully-uploaded": {
"message": "Sikeresen feltöltve"
},
"app.instance.worlds.add-server": {
"message": "Szerver hozzáadása"
"app.instance.mods.unknown-version": {
"message": "Ismeretlen"
},
"app.instance.worlds.browse-servers": {
"message": "Szerverek böngészése"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' **véglegesen törlődik**, és azt semmilyen módon nem lehet majd helyreállítani."
},
"app.instance.worlds.delete-world-title": {
"message": "Biztosan véglegesen törölni szeretnéd ezt a világot?"
},
"app.instance.worlds.filter-modded": {
"message": "Modolt"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Kezdéshez adj hozzá egy szervert, vagy böngéssz"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nincs szerver vagy világ"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' eltávolításra kerül a listádról, beleértve a játékon belülieket is. Ezt a lépést nem tudod visszavonni."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) eltávolításra kerül a listádról, beleértve a játékon belülieket is. Ezt a lépést nem tudod visszavonni."
},
"app.instance.worlds.remove-server-title": {
"message": "Biztosan el akarod távolítani ezt: {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Keresés {count} világ között..."
},
"app.instance.worlds.this-server": {
"message": "ez a szerver"
"app.instance.mods.updating": {
"message": "Frissítés..."
},
"app.modal.install-to-play.content-required": {
"message": "Szükséges tartalom"
@@ -159,7 +102,7 @@
"message": "Telepítés"
},
"app.modal.install-to-play.mod-count": {
"message": "{count} mod"
"message": "{count, plural,one {# mod}other {# modok}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Szükséges modcsomag"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Alapértelmezett profil beállítások"
},
"app.settings.tabs.feature-flags": {
"message": "Funkciójelzők"
},
"app.settings.tabs.java-installations": {
"message": "Telepített Java példányok"
},
@@ -228,7 +174,7 @@
"message": "Letöltés ({size})"
},
"app.update-popup.download-complete": {
"message": "Sikeres letöltés"
"message": "Letöltés sikeres"
},
"app.update-popup.reload": {
"message": "Újratöltés"
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "A telepítéshez újraindítás szükséges"
},
"app.world.server-modal.placeholder-address": {
"message": "pelda.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Válassz egy lehetőséget"
},
"app.world.world-item.incompatible-version": {
"message": "Inkompatibilis verzió: {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Még nem játszottál"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Barát hozzáadása"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Világ szerkesztése"
},
"instance.files.adding-files": {
"message": "Fájlok hozzáadása ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Mentés másként..."
},
"instance.server-modal.address": {
"message": "Cím"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Ne mutasd a kezdőlapon"
},
"instance.worlds.filter.available": {
"message": "Elérhető"
},
"instance.worlds.game_already_open": {
"message": "A profil már meg van nyitva"
},
@@ -552,13 +477,13 @@
"message": "Hardcore mód"
},
"instance.worlds.incompatible_server": {
"message": "A szerver nem kompatibilis"
"message": "A Szerver nem kompatibilis"
},
"instance.worlds.linked_server": {
"message": "Szerverprojekt által kezelt"
},
"instance.worlds.no_contact": {
"message": "A szerverrel nem lehet kapcsolatot létesíteni"
"message": "Nem lehet kapcsolatot létesíteni a szerverrel"
},
"instance.worlds.no_server_quick_play": {
"message": "Csak Minecraft Alpha 1.0.5+-tól tudsz egyből szerverhez csatlakozni"
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Játék a profillal"
},
"instance.worlds.type.server": {
"message": "Szerver"
},
"instance.worlds.type.singleplayer": {
"message": "Egyjátékos"
},
"instance.worlds.view_instance": {
"message": "Profil megtekintése"
},
@@ -582,13 +513,13 @@
"message": "A játékverzió a profil által van megadva"
},
"search.filter.locked.instance-loader.title": {
"message": "A betöltő a profil által van megadva"
"message": "A modbetöltő a profil által van megadva"
},
"search.filter.locked.instance.sync": {
"message": "Profil szinkronizálása"
},
"search.filter.locked.server": {
"message": "A szerver által van megadva"
"message": "A szerver biztosítja"
},
"search.filter.locked.server-environment.title": {
"message": "Csak kliensoldali modok adhatók hozzá a szerverprofilhoz"
@@ -41,12 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Hapus instans"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Paket mod ini telah terpasang pada instans \"{instanceName}\"."
},
"app.instance.modpack-already-installed.admonition-header": {
"message": "Gandakan paket mod"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Tetap buat"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Pergi ke instans"
},
"app.instance.modpack-already-installed.header": {
"message": "Paket mod telah terpasang"
},
"app.instance.mods.content-type-project": {
"message": "proyek"
},
"app.instance.mods.copy-link": {
"message": "Salin tautan"
},
"app.instance.mods.installing": {
"message": "Memasang..."
},
"app.instance.mods.modpack-fallback": {
"message": "Paket Mod"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" telah ditambahkan"
},
@@ -59,9 +80,18 @@
"app.instance.mods.share-title": {
"message": "Membagi konten paket mod"
},
"app.instance.mods.show-file": {
"message": "Tampilkan berkas"
},
"app.instance.mods.successfully-uploaded": {
"message": "Berhasil diunggah"
},
"app.instance.mods.unknown-version": {
"message": "Tidak diketahui"
},
"app.instance.mods.updating": {
"message": "Memperbarui..."
},
"app.modal.install-to-play.content-required": {
"message": "Konten diperlukan"
},
@@ -110,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Pilihan instans bawaan"
},
"app.settings.tabs.feature-flags": {
"message": "Bendera fitur"
},
"app.settings.tabs.java-installations": {
"message": "Pemasangan Java"
},
@@ -434,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Jangan tampilkan di Beranda"
},
"instance.worlds.filter.available": {
"message": "Tersedia"
},
"instance.worlds.game_already_open": {
"message": "Instans telah dibuka"
},
@@ -458,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Mainkan instans"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Bermain sendiri"
},
"instance.worlds.view_instance": {
"message": "Lihat instans"
},
+39 -108
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Impossibile raggiungere i server di autenticazione"
},
"app.browse.add-server-to-instance": {
"message": "Aggiungi server all'istanza"
},
"app.browse.add-servers-to-instance": {
"message": "Aggiungi i server alla tua istanza"
},
"app.browse.add-to-instance": {
"message": "Aggiungi all'istanza"
},
"app.browse.add-to-instance-name": {
"message": "Aggiungi a {instanceName}"
},
"app.browse.added": {
"message": "Aggiunto"
},
"app.browse.already-added": {
"message": "Già aggiunto"
},
"app.browse.discover-content": {
"message": "Esplora i contenuti"
},
"app.browse.discover-servers": {
"message": "Esplora i server"
},
"app.browse.hide-added-servers": {
"message": "Nascondi server aggiunti"
},
"app.browse.hide-installed-content": {
"message": "Nascondi contenuti installati"
},
"app.browse.install-content-to-instance": {
"message": "Installa contenuti nell'istanza"
},
"app.export-modal.description-placeholder": {
"message": "Inserisci descrizione del pacchetto..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Elimina istanza"
},
"app.instance.modpack-already-installed.body": {
"message": "Questo pacchetto di mod è già installato nell'istanza <bold>{instanceName}</bold>. Vuoi davvero duplicarlo?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Questo pacchetto è già stato installato nell'istanza \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Crea"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Pacchetto duplicato"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Crea comunque"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Vai all'istanza"
},
"app.instance.modpack-already-installed.header": {
"message": "Pacchetto già installato"
},
"app.instance.modpack-already-installed.instance": {
"message": "Istanza"
},
"app.instance.mods.content-type-project": {
"message": "progetto"
},
"app.instance.mods.copy-link": {
"message": "Copia link"
},
"app.instance.mods.installing": {
"message": "Installando..."
},
"app.instance.mods.modpack-fallback": {
"message": "Pacchetto di mod"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" è stato aggiunto"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Condivisione del pacchetto"
},
"app.instance.mods.show-file": {
"message": "Mostra file"
},
"app.instance.mods.successfully-uploaded": {
"message": "Caricato con successo"
},
"app.instance.worlds.add-server": {
"message": "Aggiungi server"
"app.instance.mods.unknown-version": {
"message": "Sconosciuta"
},
"app.instance.worlds.browse-servers": {
"message": "Esplora i server"
},
"app.instance.worlds.delete-world-description": {
"message": "\"{name}\" verrà **eliminato permanentemente** e non ci sarà modo di recuperarlo."
},
"app.instance.worlds.delete-world-title": {
"message": "Vuoi davvero eliminare questo mondo per sempre?"
},
"app.instance.worlds.filter-modded": {
"message": "Moddato"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Inizia esplorando o aggiungendo un server"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nessun server o mondo aggiunto"
},
"app.instance.worlds.remove-server-description": {
"message": "\"{name}\" sarà rimosso dalla tua lista, inclusa quella nel gioco, e non ci sarà modo di recuperarlo."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "\"{name}\" ({address}) sarà rimosso dalla tua lista, inclusa quella nel gioco, e non ci sarà modo di recuperarlo."
},
"app.instance.worlds.remove-server-title": {
"message": "Vuoi davvero rimuovere {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cerca tra {count} mondi..."
},
"app.instance.worlds.this-server": {
"message": "questo server"
"app.instance.mods.updating": {
"message": "Aggiornando..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenuto richiesto"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Opzioni d'istanza predefinite"
},
"app.settings.tabs.feature-flags": {
"message": "Flag di funzionalità"
},
"app.settings.tabs.java-installations": {
"message": "Installazioni Java"
},
@@ -246,29 +192,11 @@
"message": "Scarica aggiornamento"
},
"app.update.downloading-update": {
"message": "Scaricando l'aggiornamento ({percent}%)"
"message": "Scaricando aggiornamento ({percent}%)"
},
"app.update.reload-to-update": {
"message": "Ricarica per installare l'aggiornamento"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Seleziona un'opzione"
},
"app.world.world-item.incompatible-version": {
"message": "Versione {version} incompatibile"
},
"app.world.world-item.not-played-yet": {
"message": "Mai avviato"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Stringi un'amicizia"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Modifica mondo"
},
"instance.files.adding-files": {
"message": "Aggiungendo i file ({completed}/{total})..."
},
"instance.files.save-as": {
"message": "Salva come..."
},
"instance.server-modal.address": {
"message": "Indirizzo"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Non mostrare nella home"
},
"instance.worlds.filter.available": {
"message": "Disponibile"
},
"instance.worlds.game_already_open": {
"message": "Istanza già aperta"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Avvia istanza"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Giocatore singolo"
},
"instance.worlds.view_instance": {
"message": "Mostra istanza"
},
+40 -109
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "認証サーバーにアクセスできません"
},
"app.browse.add-server-to-instance": {
"message": "サーバーをインスタンスに追加"
},
"app.browse.add-servers-to-instance": {
"message": "サーバーを自身のインスタンスに追加"
},
"app.browse.add-to-instance": {
"message": "インスタンスに追加"
},
"app.browse.add-to-instance-name": {
"message": "{instanceName} に追加"
},
"app.browse.added": {
"message": "追加済み"
},
"app.browse.already-added": {
"message": "すでに追加済み"
},
"app.browse.discover-content": {
"message": "コンテンツを探す"
},
"app.browse.discover-servers": {
"message": "サーバーを探す"
},
"app.browse.hide-added-servers": {
"message": "追加済みのサーバーを隠す"
},
"app.browse.hide-installed-content": {
"message": "インストール済みのコンテンツを隠す"
},
"app.browse.install-content-to-instance": {
"message": "コンテンツをインスタンスにインストールする"
},
"app.export-modal.description-placeholder": {
"message": "Modパックの説明を入力…"
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "インスタンスを削除"
},
"app.instance.modpack-already-installed.body": {
"message": "このModpackは既に <bold>{instanceName}</bold> インスタンスにインストール済みです。本当に複製しますか?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "このModパックは「{instanceName}インスタンスにすでにインストールされています。"
},
"app.instance.modpack-already-installed.create": {
"message": "新規作成"
"app.instance.modpack-already-installed.admonition-header": {
"message": "重複したModパック"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "とにかく作成する"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "インスタンスに移動"
},
"app.instance.modpack-already-installed.header": {
"message": "Modパックはすでにインストール済み"
},
"app.instance.modpack-already-installed.instance": {
"message": "インスタンス"
},
"app.instance.mods.content-type-project": {
"message": "プロジェクト"
},
"app.instance.mods.copy-link": {
"message": "リンクをコピー"
},
"app.instance.mods.installing": {
"message": "インストール中…"
},
"app.instance.mods.modpack-fallback": {
"message": "Modパック"
},
"app.instance.mods.project-was-added": {
"message": "「{name}」が追加されました"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Modパックのコンテンツを共有"
},
"app.instance.mods.show-file": {
"message": "ファイルを表示"
},
"app.instance.mods.successfully-uploaded": {
"message": "アップロードに成功しました"
},
"app.instance.worlds.add-server": {
"message": "サーバーを追加"
"app.instance.mods.unknown-version": {
"message": "不明"
},
"app.instance.worlds.browse-servers": {
"message": "サーバーを探す"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' は **完全に削除**され、元に戻すことはできません。"
},
"app.instance.worlds.delete-world-title": {
"message": "本当にこのワールドを完全に削除しますか?"
},
"app.instance.worlds.filter-modded": {
"message": "Mod導入済み"
},
"app.instance.worlds.filter-offline": {
"message": "オフライン"
},
"app.instance.worlds.filter-online": {
"message": "オンライン"
},
"app.instance.worlds.filter-vanilla": {
"message": "バニラ"
},
"app.instance.worlds.no-worlds-description": {
"message": "サーバーを追加、またはブラウズして始める"
},
"app.instance.worlds.no-worlds-heading": {
"message": "サーバー、ワールドは追加されていません"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' はゲーム内を含むサーバーリストから削除され、元に戻すことはできません。"
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) はゲーム内を含むサーバーリストから削除され、元に戻すことはできません。"
},
"app.instance.worlds.remove-server-title": {
"message": "本当に {name} を削除しますか?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count} 個のワールドを検索…"
},
"app.instance.worlds.this-server": {
"message": "このサーバー"
"app.instance.mods.updating": {
"message": "アップデート中…"
},
"app.modal.install-to-play.content-required": {
"message": "必須コンテンツ"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "インスタンスの基本設定"
},
"app.settings.tabs.feature-flags": {
"message": "機能設定"
},
"app.settings.tabs.java-installations": {
"message": "Javaのインストール設定"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "再起動して今すぐ更新"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "オプションを選択"
},
"app.world.world-item.incompatible-version": {
"message": "互換性の無いバージョン {version}"
},
"app.world.world-item.not-played-yet": {
"message": "未プレイ"
},
"app.world.world-item.offline": {
"message": "オフライン"
},
"app.world.world-item.players-online": {
"message": "{count} 人がオンライン"
},
"friends.action.add-friend": {
"message": "フレンドを追加"
},
@@ -282,10 +210,10 @@
"message": "フレンドを追加中"
},
"friends.add-friend.username.description": {
"message": "これはMinecraftユーザー名とは異なる可能性があります!"
"message": "これはMinecraftユーザーネームと違う可能性があります!"
},
"friends.add-friend.username.placeholder": {
"message": "Modrinthユーザー名を入力…"
"message": "Modrinthユーザーネームを入力..."
},
"friends.add-friend.username.title": {
"message": "あなたのフレンドのModrinthユーザー名は何ですか?"
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "ワールドを編集"
},
"instance.files.adding-files": {
"message": "ファイルを追加中 ({completed}/{total})"
},
"instance.files.save-as": {
"message": "名前をつけて保存…"
},
"instance.server-modal.address": {
"message": "アドレス"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "ホームに表示しない"
},
"instance.worlds.filter.available": {
"message": "利用可能"
},
"instance.worlds.game_already_open": {
"message": "インスタンスは既に開いています"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "インスタンスを起動"
},
"instance.worlds.type.server": {
"message": "サーバー"
},
"instance.worlds.type.singleplayer": {
"message": "シングルプレイ"
},
"instance.worlds.view_instance": {
"message": "インスタンスを表示する"
},
+18 -171
View File
@@ -5,149 +5,8 @@
"app.auth-servers.unreachable.header": {
"message": "인증 서버에 연결할 수 없습니다"
},
"app.browse.add-server-to-instance": {
"message": "인스턴스에 서버 추가"
},
"app.browse.add-servers-to-instance": {
"message": "인스턴스에 서버 추가"
},
"app.browse.add-to-instance": {
"message": "인스턴스에 추가"
},
"app.browse.add-to-instance-name": {
"message": "{instanceName}에 추가"
},
"app.browse.added": {
"message": "추가됨"
},
"app.browse.already-added": {
"message": "이미 추가됨"
},
"app.browse.discover-content": {
"message": "콘텐츠 탐색하기"
},
"app.browse.discover-servers": {
"message": "서버 탐색하기"
},
"app.browse.hide-added-servers": {
"message": "추가된 서버 숨기기"
},
"app.browse.hide-installed-content": {
"message": "설치된 콘텐츠 숨기기"
},
"app.browse.install-content-to-instance": {
"message": "인스턴스에 콘텐츠 설치"
},
"app.export-modal.description-placeholder": {
"message": "모드팩 설명 입력..."
},
"app.export-modal.export-button": {
"message": "내보내기"
},
"app.export-modal.header": {
"message": "모드팩 내보내기"
},
"app.export-modal.modpack-name-label": {
"message": "모드팩 이름"
},
"app.export-modal.modpack-name-placeholder": {
"message": "모드팩 이름"
},
"app.export-modal.select-files-label": {
"message": "팩에 포함할 파일과 폴더 선택"
},
"app.export-modal.version-number-label": {
"message": "버전 구분"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "인스턴스의 모든 데이터가 삭제됩니다. 세계 폴더, 설정 폴더 등 설치된 모든 컨텐츠가 포함됩니다."
},
"app.instance.confirm-delete.admonition-header": {
"message": "이 작업은 되돌릴 수 없습니다"
},
"app.instance.confirm-delete.delete-button": {
"message": "인스턴스 삭제"
},
"app.instance.confirm-delete.header": {
"message": "인스턴스 삭제"
},
"app.instance.modpack-already-installed.body": {
"message": "이 모드팩은 이미 <bold>{instanceName}</bold> 인스턴스에 설치되어 있습니다. 정말로 복제하시겠습니까?"
},
"app.instance.modpack-already-installed.create": {
"message": "생성"
},
"app.instance.modpack-already-installed.header": {
"message": "모드팩 이미 설치됨"
},
"app.instance.modpack-already-installed.instance": {
"message": "인스턴스"
},
"app.instance.mods.content-type-project": {
"message": "프로젝트"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" 추가됨"
},
"app.instance.mods.projects-were-added": {
"message": "프로젝트 {count}개 추가됨"
},
"app.instance.mods.share-text": {
"message": "제 모드팩에 사용중인 프로젝트를 확인해 보세요!"
},
"app.instance.mods.share-title": {
"message": "모드팩 콘텐츠 공유"
},
"app.instance.mods.successfully-uploaded": {
"message": "업로드 성공"
},
"app.instance.worlds.add-server": {
"message": "서버 추가"
},
"app.instance.worlds.browse-servers": {
"message": "서버 탐색하기"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}'이(가) **영구적으로 삭제**되며, 복구할 수 없습니다."
},
"app.instance.worlds.delete-world-title": {
"message": "정말로 이 세계를 영구적으로 삭제하시겠습니까?"
},
"app.instance.worlds.filter-modded": {
"message": "수정됨"
},
"app.instance.worlds.filter-offline": {
"message": "오프라인"
},
"app.instance.worlds.filter-online": {
"message": "온라인"
},
"app.instance.worlds.filter-vanilla": {
"message": "바닐라"
},
"app.instance.worlds.no-worlds-description": {
"message": "서버를 추가하거나 탐색하여 시작"
},
"app.instance.worlds.no-worlds-heading": {
"message": "추가된 서버 또는 세계 없음"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}'이(가) 게임 내를 포함한 모든 목록에서 삭제되며, 복구할 수 없습니다."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address})이(가) 게임 내를 포함한 모든 목록에서 삭제되며, 복구할 수 없습니다."
},
"app.instance.worlds.remove-server-title": {
"message": "정말로 {name}을(를) 제거하시겠습니까?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count}개의 세계 검색..."
},
"app.instance.worlds.this-server": {
"message": "이 서버"
"app.instance.mods.updating": {
"message": "업데이트 중..."
},
"app.modal.install-to-play.content-required": {
"message": "콘텐츠 설치 필요"
@@ -197,6 +56,9 @@
"app.settings.tabs.default-instance-options": {
"message": "기본 인스턴스 옵션"
},
"app.settings.tabs.feature-flags": {
"message": "기능 플래그"
},
"app.settings.tabs.java-installations": {
"message": "Java 설치"
},
@@ -216,7 +78,7 @@
"message": "Modrinth App v{version} 다운로드가 완료되었습니다. 새로고침하거나 Modrinth App을 종료하면 자동으로 업데이트됩니다."
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version}을 사용할 수 있습니다. 최신 기능과 오류 수정을 적용하려면 패키지 관리자를 통해 업데이트하세요!"
"message": "Modrinth App v{version} 버전을 사용할 수 있습니다. 최신 기능과 오류 수정을 적용하려면 패키지 관리자를 통해 업데이트하세요!"
},
"app.update-popup.body.metered": {
"message": "Modrinth App v{version}을 지금 사용할 수 있습니다! 데이터 통신 연결을 사용 중이므로, 자동으로 다운로드하지 않았습니다."
@@ -225,7 +87,7 @@
"message": "변경 내역"
},
"app.update-popup.download": {
"message": "다운로드 ({size})"
"message": "({size}) 다운로드"
},
"app.update-popup.download-complete": {
"message": "다운로드 완료"
@@ -251,24 +113,6 @@
"app.update.reload-to-update": {
"message": "새로고침하여 업데이트 설치"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "옵션 선택"
},
"app.world.world-item.incompatible-version": {
"message": "{version} 버전에 호환되지 않음"
},
"app.world.world-item.not-played-yet": {
"message": "플레이 안 함"
},
"app.world.world-item.offline": {
"message": "오프라인"
},
"app.world.world-item.players-online": {
"message": "{count}명 온라인"
},
"friends.action.add-friend": {
"message": "친구 추가"
},
@@ -321,7 +165,7 @@
"message": "대기 중"
},
"friends.no-friends-match": {
"message": "\"{query}\"과(와) 일치하는 친구 없음"
"message": "\"{query}\" 이름과 일치하는 친구 없음"
},
"friends.search-friends-placeholder": {
"message": "친구 검색 중..."
@@ -368,12 +212,6 @@
"instance.edit-world.title": {
"message": "세계 편집"
},
"instance.files.adding-files": {
"message": "파일 추가 중 ({completed}/{total})"
},
"instance.files.save-as": {
"message": "다른 이름으로 저장..."
},
"instance.server-modal.address": {
"message": "주소"
},
@@ -420,7 +258,7 @@
"message": "아이콘 제거"
},
"instance.settings.tabs.general.edit-icon.replace": {
"message": "아이콘 체"
"message": "아이콘 체"
},
"instance.settings.tabs.general.edit-icon.select": {
"message": "아이콘 선택"
@@ -545,6 +383,9 @@
"instance.worlds.dont_show_on_home": {
"message": "홈에서 숨기기"
},
"instance.worlds.filter.available": {
"message": "이용 가능"
},
"instance.worlds.game_already_open": {
"message": "이미 실행된 인스턴스"
},
@@ -569,6 +410,12 @@
"instance.worlds.play_instance": {
"message": "인스턴스 플레이"
},
"instance.worlds.type.server": {
"message": "서버"
},
"instance.worlds.type.singleplayer": {
"message": "싱글 플레이"
},
"instance.worlds.view_instance": {
"message": "인스턴스 보기"
},
+32 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Tidak dapat mencapai pelayan pengesahan"
},
"app.browse.add-server-to-instance": {
"message": "Tambahkan pelayan ke dalam pemasangan"
},
"app.browse.add-servers-to-instance": {
"message": "Tambahkan pelayan ke dalam pemasangan anda"
},
"app.browse.add-to-instance": {
"message": "Tambahkan ke dalam pemasangan"
},
"app.browse.add-to-instance-name": {
"message": "Tambahkan ke {instanceName}"
},
"app.browse.added": {
"message": "Ditambah"
},
"app.browse.already-added": {
"message": "Sudah ditambah"
},
"app.browse.discover-content": {
"message": "Temui kandungan"
},
"app.browse.discover-servers": {
"message": "Temui pelayan"
},
"app.browse.hide-added-servers": {
"message": "Sembunyikan pelayan yang ditambah"
},
"app.browse.hide-installed-content": {
"message": "Sembunyikan kandungan yang dipasang"
},
"app.browse.install-content-to-instance": {
"message": "Pasang kandungan ke dalam pemasangan"
},
"app.export-modal.description-placeholder": {
"message": "Masukkan keterangan pek mod..."
},
@@ -74,21 +41,27 @@
"app.instance.confirm-delete.header": {
"message": "Padam pemasangan"
},
"app.instance.modpack-already-installed.body": {
"message": "Pek mod ini telah dipasang dalam pemasangan <bold>{instanceName}</bold>. Adakah anda pasti mahu menduakannya?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Pek mod ini sudah pun dipasang di dalam pemasangan \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Cipta"
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Pergi ke pemasangan"
},
"app.instance.modpack-already-installed.header": {
"message": "Pek mod sudah dipasang"
},
"app.instance.modpack-already-installed.instance": {
"message": "Pemasangan"
},
"app.instance.mods.content-type-project": {
"message": "projek"
},
"app.instance.mods.copy-link": {
"message": "Salin pautan"
},
"app.instance.mods.installing": {
"message": "Sedang memasang..."
},
"app.instance.mods.modpack-fallback": {
"message": "Pek Mod"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" telah ditambahkan"
},
@@ -101,53 +74,17 @@
"app.instance.mods.share-title": {
"message": "Berkongsi kandungan pek mod"
},
"app.instance.mods.show-file": {
"message": "Tunjukkan fail"
},
"app.instance.mods.successfully-uploaded": {
"message": "Berjaya dimuat naik"
},
"app.instance.worlds.add-server": {
"message": "Tambah pelayan"
"app.instance.mods.unknown-version": {
"message": "Tidak Diketahui"
},
"app.instance.worlds.browse-servers": {
"message": "Layari pelayan"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' akan **dipadamkan secara kekal**, dan tiada cara untuk memulihkannya."
},
"app.instance.worlds.delete-world-title": {
"message": "Adakah anda pasti mahu memadamkan dunia ini secara kekal?"
},
"app.instance.worlds.filter-modded": {
"message": "Diubah Suai"
},
"app.instance.worlds.filter-offline": {
"message": "Luar Talian"
},
"app.instance.worlds.filter-online": {
"message": "Dalam Talian"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanila"
},
"app.instance.worlds.no-worlds-description": {
"message": "Tambah sebuah pelayan atau layari untuk bermula"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Tiada pelayan atau dunia ditambah"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' akan dialih keluar daripada senarai anda, termasuk dalam permainan, dan tiada cara untuk memulihkannya."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) akan dialih keluar daripada senarai anda, termasuk dalam permainan, dan tiada cara untuk memulihkannya."
},
"app.instance.worlds.remove-server-title": {
"message": "Adakah anda pasti mahu mengalih keluar {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cari {count} dunia..."
},
"app.instance.worlds.this-server": {
"message": "pelayan ini"
"app.instance.mods.updating": {
"message": "Sedang mengemas kini..."
},
"app.modal.install-to-play.content-required": {
"message": "Kandungan yang diperlukan"
@@ -197,6 +134,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Tetapan lalai pemasangan"
},
"app.settings.tabs.feature-flags": {
"message": "Penetapan ciri"
},
"app.settings.tabs.java-installations": {
"message": "Pemasangan Java"
},
@@ -251,24 +191,6 @@
"app.update.reload-to-update": {
"message": "Muat semula untuk memasang kemas kini"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Pilih pilihan anda"
},
"app.world.world-item.incompatible-version": {
"message": "Versi tidak serasi: {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Belum dimainkan"
},
"app.world.world-item.offline": {
"message": "Luar Talian"
},
"app.world.world-item.players-online": {
"message": "{count} dalam talian"
},
"friends.action.add-friend": {
"message": "Tambah rakan"
},
@@ -368,12 +290,6 @@
"instance.edit-world.title": {
"message": "Sunting dunia"
},
"instance.files.adding-files": {
"message": "Sedang menambah fail ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Simpan sebagai..."
},
"instance.server-modal.address": {
"message": "Alamat"
},
@@ -545,6 +461,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Jangan tunjukkan pada Laman Utama"
},
"instance.worlds.filter.available": {
"message": "Tersedia"
},
"instance.worlds.game_already_open": {
"message": "Pemasangan sudah pun dibuka"
},
@@ -569,6 +488,12 @@
"instance.worlds.play_instance": {
"message": "Mainkan pemasangan"
},
"instance.worlds.type.server": {
"message": "Pelayan"
},
"instance.worlds.type.singleplayer": {
"message": "Ekapemain"
},
"instance.worlds.view_instance": {
"message": "Lihat pemasangan"
},
+38 -98
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Authenticatieservers kunnen niet worden bereikt"
},
"app.browse.add-server-to-instance": {
"message": "Voeg server aan instantie toe"
},
"app.browse.add-servers-to-instance": {
"message": "Voeg servers aan instantie toe"
},
"app.browse.add-to-instance": {
"message": "Voeg aan instantie toe"
},
"app.browse.add-to-instance-name": {
"message": "Voeg aan {instanceName} toe"
},
"app.browse.added": {
"message": "Toegevoegd"
},
"app.browse.already-added": {
"message": "Al toegevoegd"
},
"app.browse.discover-content": {
"message": "Ontdek content"
},
"app.browse.discover-servers": {
"message": "Ontdek servers"
},
"app.browse.hide-added-servers": {
"message": "Verstop toegevoegde servers"
},
"app.browse.hide-installed-content": {
"message": "Verberg Geïnstalleerde inhoud"
},
"app.browse.install-content-to-instance": {
"message": "Installeer inhoud naar instantie"
},
"app.export-modal.description-placeholder": {
"message": "Voeg modpack beschrijving in..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Verwijder instantie"
},
"app.instance.modpack-already-installed.body": {
"message": "Deze modpakket is al in de <bold>{instanceName}<bold> instantie geïnstalleerd. Ben je zeker dat je hetgeen wil dupliceren?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Deze modpack is al geïnstalleerd in de \"{instanceName}\" instantie."
},
"app.instance.modpack-already-installed.create": {
"message": "Maak"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Dupliceer modpack"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Maak toch"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Ga naar instantie"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack is al geïnstalleerd"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instantie"
},
"app.instance.mods.content-type-project": {
"message": "project"
},
"app.instance.mods.copy-link": {
"message": "Kopieer link"
},
"app.instance.mods.installing": {
"message": "Aan het installeren..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" was toegevoegd"
},
@@ -101,44 +80,17 @@
"app.instance.mods.share-title": {
"message": "Modpack-inhoud delen"
},
"app.instance.mods.show-file": {
"message": "Bestand weergeven"
},
"app.instance.mods.successfully-uploaded": {
"message": "Succesvol geüpload"
},
"app.instance.worlds.add-server": {
"message": "Voeg server toe"
"app.instance.mods.unknown-version": {
"message": "Onbekend"
},
"app.instance.worlds.browse-servers": {
"message": "Zoek servers"
},
"app.instance.worlds.delete-world-description": {
"message": "{name} zal **permanent verwijderd** worden en kan niet hersteld worden."
},
"app.instance.worlds.filter-modded": {
"message": "Gemod"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Voeg of zoek een server om te beginnen"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Noch servers noch werelden toegevoegd"
},
"app.instance.worlds.remove-server-title": {
"message": "Ben je zeker dat je {name} wil verwijderen?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Zoek werelden"
},
"app.instance.worlds.this-server": {
"message": "deze server"
"app.instance.mods.updating": {
"message": "Aan het updaten..."
},
"app.modal.install-to-play.content-required": {
"message": "Content vereist"
@@ -188,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Standaardopties voor instantie"
},
"app.settings.tabs.feature-flags": {
"message": "Feature flags"
},
"app.settings.tabs.java-installations": {
"message": "Java installaties"
},
@@ -242,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Herlaad om de update te installeren"
},
"app.world.server-modal.placeholder-address": {
"message": "voorbeeld.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Kies een keuze"
},
"app.world.world-item.incompatible-version": {
"message": "Incompatibele versie{version}"
},
"app.world.world-item.not-played-yet": {
"message": "Nog niet gespeeld"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Voeg een vriend toe"
},
@@ -359,12 +296,6 @@
"instance.edit-world.title": {
"message": "Wereld bewerken"
},
"instance.files.adding-files": {
"message": "Bestanden toevoegen ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Opslaan als..."
},
"instance.server-modal.address": {
"message": "Adres"
},
@@ -536,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Niet tonen op de startpagina"
},
"instance.worlds.filter.available": {
"message": "Beschikbaar"
},
"instance.worlds.game_already_open": {
"message": "Instantie is al open"
},
@@ -560,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Speel instantie"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Alleen spelen"
},
"instance.worlds.view_instance": {
"message": "Bekijk instantie"
},
@@ -41,6 +41,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Standard instansinstillinger"
},
"app.settings.tabs.feature-flags": {
"message": "Funksjonsflagg"
},
"app.settings.tabs.java-installations": {
"message": "Java installasjoner"
},
@@ -338,6 +341,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Ikke vis på Hjem"
},
"instance.worlds.filter.available": {
"message": "Tilgjengelig"
},
"instance.worlds.game_already_open": {
"message": "Instansen er allerede open"
},
@@ -359,6 +365,12 @@
"instance.worlds.play_instance": {
"message": "Spill instans"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Enkeltspillermodus"
},
"instance.worlds.view_instance": {
"message": "Vis instans"
},
+33 -117
View File
@@ -5,42 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Nie udało się połączyć się z serwerami uwierzytelniania"
},
"app.browse.add-server-to-instance": {
"message": "Dodaj serwer do instancji"
},
"app.browse.add-servers-to-instance": {
"message": "Dodaj serwery do swojej instancji"
},
"app.browse.add-to-instance": {
"message": "Dodaj do instancji"
},
"app.browse.add-to-instance-name": {
"message": "Dodaj do {instanceName}"
},
"app.browse.added": {
"message": "Dodano"
},
"app.browse.already-added": {
"message": "Już dodano"
},
"app.browse.discover-content": {
"message": "Odkrywaj zawartość"
},
"app.browse.discover-servers": {
"message": "Odkryj serwery"
},
"app.browse.hide-added-servers": {
"message": "Ukryj dodane serwery"
},
"app.browse.hide-installed-content": {
"message": "Ukryj zainstalowane zasoby"
},
"app.browse.install-content-to-instance": {
"message": "Zainstaluj zasoby do instancji"
},
"app.export-modal.description-placeholder": {
"message": "Wprowadź opis paczki modów..."
},
"app.export-modal.export-button": {
"message": "Eksportuj"
},
@@ -74,81 +38,45 @@
"app.instance.confirm-delete.header": {
"message": "Usuń instancję"
},
"app.instance.modpack-already-installed.body": {
"message": "Ta paczka modów jest już zainstalowana w instancji <bold>{instanceName}</bold>. Czy na pewno chcesz utworzyć duplikat?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Ta paczka modów jest już zainstalowana w instancji \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Utwórz"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Duplikat paczki modów"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Utwórz mimo to"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Przejdź do instancji"
},
"app.instance.modpack-already-installed.header": {
"message": "Paczka modów jest już zainstalowana"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instalacja"
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
"app.instance.mods.copy-link": {
"message": "Kopiuj łącze"
},
"app.instance.mods.installing": {
"message": "Instalowanie..."
},
"app.instance.mods.modpack-fallback": {
"message": "Paczka modów"
},
"app.instance.mods.project-was-added": {
"message": "Dodano \"{name}\""
},
"app.instance.mods.projects-were-added": {
"message": "Dodano {count} {count, plural, one {projekt} few {projekty} other {projektów}}"
},
"app.instance.mods.share-text": {
"message": "Sprawdź projekty, które używam w mojej paczce modów!"
},
"app.instance.mods.share-title": {
"message": "Udostępnianie zawartości paczki modów"
"app.instance.mods.show-file": {
"message": "Pokaż plik"
},
"app.instance.mods.successfully-uploaded": {
"message": "Przesyłanie powiodło się"
},
"app.instance.worlds.add-server": {
"message": "Dodaj serwer"
},
"app.instance.worlds.browse-servers": {
"message": "Szukaj serwerów"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' zostanie **permanentnie usunięty** i nie ma możliwości go odzyskać."
},
"app.instance.worlds.delete-world-title": {
"message": "Czy na pewno chcesz trwale usunąć ten świat?"
},
"app.instance.worlds.filter-modded": {
"message": "Zmodowany"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Dodaj serwer lub szukaj, aby rozpocząć"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nie dodano żadnych światów ani serwerów"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' zostanie usunięty z twojej listy, również z gry, i nie będzie możliwości go odzyskać."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) zostanie usunięty z twojej listy, również z gry, i nie będzie możliwości go odzyskać."
},
"app.instance.worlds.remove-server-title": {
"message": "Czy jesteś pewny, że chcesz usunąć {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Szukaj wśród {count} światów..."
},
"app.instance.worlds.this-server": {
"message": "ten serwer"
},
"app.modal.install-to-play.content-required": {
"message": "Wymagane treści"
},
@@ -197,6 +125,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Domyślne opcje instancji"
},
"app.settings.tabs.feature-flags": {
"message": "Flagi funkcji"
},
"app.settings.tabs.java-installations": {
"message": "Instalacje Java"
},
@@ -251,24 +182,6 @@
"app.update.reload-to-update": {
"message": "Załaduj ponownie, aby zainstalować aktualizację"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Wybierz opcję"
},
"app.world.world-item.incompatible-version": {
"message": "Niekompatybilna wersja {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Jeszcze nie grano"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Dodaj znajomego"
},
@@ -368,12 +281,6 @@
"instance.edit-world.title": {
"message": "Edytuj świat"
},
"instance.files.adding-files": {
"message": "Dodawanie plików ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Zapisz jako..."
},
"instance.server-modal.address": {
"message": "Adres"
},
@@ -545,6 +452,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Nie pokazuj na stronie głównej"
},
"instance.worlds.filter.available": {
"message": "Dostępny"
},
"instance.worlds.game_already_open": {
"message": "Instancja jest już otwarta"
},
@@ -569,6 +479,12 @@
"instance.worlds.play_instance": {
"message": "Zagraj"
},
"instance.worlds.type.server": {
"message": "Serwer"
},
"instance.worlds.type.singleplayer": {
"message": "Tryb jednoosobowy"
},
"instance.worlds.view_instance": {
"message": "Zobacz instancję"
},
+39 -108
View File
@@ -1,43 +1,10 @@
{
"app.auth-servers.unreachable.body": {
"message": "Os servidores de autenticação do Minecraft podem estar indisponíveis no momento. Verifique sua conexão com a rede e tente novamente mais tarde."
"message": "Os servidores de autenticação do Minecraft podem estar indisponíveis no momento. Verifique sua conexão com a internet e tente novamente mais tarde."
},
"app.auth-servers.unreachable.header": {
"message": "Não foi possível acessar os servidores de autenticação"
},
"app.browse.add-server-to-instance": {
"message": "Adicionar servidor à instância"
},
"app.browse.add-servers-to-instance": {
"message": "Adicionar servidores à sua instância"
},
"app.browse.add-to-instance": {
"message": "Adicionar à instância"
},
"app.browse.add-to-instance-name": {
"message": "Adicionar a {instanceName}"
},
"app.browse.added": {
"message": "Adicionado"
},
"app.browse.already-added": {
"message": "Já adicionado"
},
"app.browse.discover-content": {
"message": "Descubra conteúdo"
},
"app.browse.discover-servers": {
"message": "Descubra servidores"
},
"app.browse.hide-added-servers": {
"message": "Ocultar servidores adicionados"
},
"app.browse.hide-installed-content": {
"message": "Ocultar conteúdo instalado"
},
"app.browse.install-content-to-instance": {
"message": "Instalar conteúdo na instância"
},
"app.export-modal.description-placeholder": {
"message": "Insira a descrição do pacote de mods..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Excluir instância"
},
"app.instance.modpack-already-installed.body": {
"message": "Este pacote de mods já está instalado na instância <bold>{instanceName}</bold>. Tem certeza de que deseja duplicá-lo?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Este pacote de mods já está instalado na instância \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Criar"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Pacote de mods duplicado"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Criar mesmo assim"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Ir para a instância"
},
"app.instance.modpack-already-installed.header": {
"message": "O pacote de mods já está instalado"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instância"
},
"app.instance.mods.content-type-project": {
"message": "projeto"
},
"app.instance.mods.copy-link": {
"message": "Copiar link"
},
"app.instance.mods.installing": {
"message": "Instalando..."
},
"app.instance.mods.modpack-fallback": {
"message": "Pacote de mods"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" foi adicionado"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Compartilhando conteúdo do pacote de mods\n\n\n \t\t\t\t\t\t\n \t\t\t\t\t\t\t\n \t\t\t\t\t\t\n \t\t\t\t\t\t"
},
"app.instance.mods.show-file": {
"message": "Mostrar arquivo"
},
"app.instance.mods.successfully-uploaded": {
"message": "Enviado com sucesso"
},
"app.instance.worlds.add-server": {
"message": "Adicionar servidor"
"app.instance.mods.unknown-version": {
"message": "Desconhecido"
},
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' será excluído permanentemente, e não haverá como recuperá-lo."
},
"app.instance.worlds.delete-world-title": {
"message": "Tem certeza de que deseja excluir este mundo permanentemente?"
},
"app.instance.worlds.filter-modded": {
"message": "Modificado"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Jogo padrão"
},
"app.instance.worlds.no-worlds-description": {
"message": "Adicione um servidor ou explore para começar"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nenhum servidor ou mundo adicionado"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' será removido da sua lista, inclusive no jogo, e não haverá como recuperá-lo."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) será removido da sua lista, inclusive no jogo, e não haverá como recuperá-lo."
},
"app.instance.worlds.remove-server-title": {
"message": "Tem certeza de que deseja remover {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar {count} mundos..."
},
"app.instance.worlds.this-server": {
"message": "este servidor"
"app.instance.mods.updating": {
"message": "Atualizando..."
},
"app.modal.install-to-play.content-required": {
"message": "Conteúdo necessário"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Opções da instância"
},
"app.settings.tabs.feature-flags": {
"message": "Sinalização de recursos"
},
"app.settings.tabs.java-installations": {
"message": "Instalações do Java"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Recarregue para instalar a atualização"
},
"app.world.server-modal.placeholder-address": {
"message": "exemplo.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Selecione uma opção"
},
"app.world.world-item.incompatible-version": {
"message": "Versão incompatível {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Ainda não jogado"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Adicionar um amigo"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Editar mundo"
},
"instance.files.adding-files": {
"message": "Adicionando arquivos ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Salvar como..."
},
"instance.server-modal.address": {
"message": "Endereço"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Não mostrar na tela inicial"
},
"instance.worlds.filter.available": {
"message": "Disponível"
},
"instance.worlds.game_already_open": {
"message": "A instância já está aberta"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Jogar na instância"
},
"instance.worlds.type.server": {
"message": "Servidor"
},
"instance.worlds.type.singleplayer": {
"message": "Um jogador"
},
"instance.worlds.view_instance": {
"message": "Ver instância"
},
+12 -222
View File
@@ -5,153 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Não foi possível alcançar os servidores de autenticação"
},
"app.browse.add-server-to-instance": {
"message": "Adicionar servidor à instância"
},
"app.browse.add-servers-to-instance": {
"message": "Adicionar servidores à tua instância"
},
"app.browse.add-to-instance": {
"message": "Adicionar à instância"
},
"app.browse.add-to-instance-name": {
"message": "Adicionar a {instanceName}"
},
"app.browse.added": {
"message": "Adicionado"
},
"app.browse.already-added": {
"message": "Já adicionado"
},
"app.browse.discover-content": {
"message": "Descobrir conteúdo"
},
"app.browse.discover-servers": {
"message": "Descobrir servidores"
},
"app.browse.hide-added-servers": {
"message": "Esconder servidores adicionados"
},
"app.browse.hide-installed-content": {
"message": "Esconder conteúdo instalado"
},
"app.browse.install-content-to-instance": {
"message": "Instalar conteúdo à instância"
},
"app.export-modal.description-placeholder": {
"message": "Insere a descrição do modpack..."
},
"app.export-modal.export-button": {
"message": "Exportar"
},
"app.export-modal.header": {
"message": "Exportar modpack"
},
"app.export-modal.modpack-name-label": {
"message": "Nome do Modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nome do Modpack"
},
"app.export-modal.select-files-label": {
"message": "Seleciona as pastas e ficheiros a incluir no pack"
},
"app.export-modal.version-number-label": {
"message": "Número da versão"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos os dados da tua instância vão ser permanentemente apagados, incluídos os teus mundos, definições, e todos os conteúdos instalados."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Esta ação não pode ser desfeita"
},
"app.instance.confirm-delete.delete-button": {
"message": "Apagar instância"
},
"app.instance.confirm-delete.header": {
"message": "Apagar instância"
},
"app.instance.modpack-already-installed.body": {
"message": "Este modpack já está instalado na instância <bold>{instanceName}</bold>. Tens a certeza que o queres duplicar?"
},
"app.instance.modpack-already-installed.create": {
"message": "Criar"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack já instalado"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instância"
},
"app.instance.mods.content-type-project": {
"message": "projeto"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" foi adicionado"
},
"app.instance.mods.projects-were-added": {
"message": "{count} projetos foram adicionados"
},
"app.instance.mods.share-text": {
"message": "Vê os projetos que estou a usar no meu modpack!"
},
"app.instance.mods.share-title": {
"message": "Partilhar conteúdo do modpack"
},
"app.instance.mods.successfully-uploaded": {
"message": "Carregado com sucesso"
},
"app.instance.worlds.add-server": {
"message": "Adicionar servidor"
},
"app.instance.worlds.browse-servers": {
"message": "Procurar servidores"
},
"app.instance.worlds.delete-world-description": {
"message": "\"{name}\" vai ser **apagado permanentemente**, e não haverá maneira de o recuperar."
},
"app.instance.worlds.delete-world-title": {
"message": "Tens a certeza que queres apagar permanentemente este mundo?"
},
"app.instance.worlds.filter-modded": {
"message": "Com Mods"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Sem mods"
},
"app.instance.worlds.no-worlds-description": {
"message": "Adiciona um servidor ou procura um para começar"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Sem servidores ou mundos adicionados"
},
"app.instance.worlds.remove-server-description": {
"message": "\"{name}\" vai ser removido da tua lista, incluindo dentro do jogo, e não haverá maneira de o recuperar."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "\"{name}\" ({address}) vai ser removido da tua lista, incluindo dentro do jogo, e não haverá maneira de o recuperar."
},
"app.instance.worlds.remove-server-title": {
"message": "Tens a certeza que queres remover {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Pesquisar {count} mundos..."
},
"app.instance.worlds.this-server": {
"message": "este servidor"
},
"app.modal.install-to-play.content-required": {
"message": "Conteúdo necessário"
},
"app.modal.install-to-play.header": {
"message": "Instala para jogar"
},
@@ -161,21 +14,12 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {mod} other {mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpack requerido"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Este servidor requer mods para jogares. Clica Instalar para transferir os ficheiros necessários do Modrinth, e então entrar diretamente no servidor."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instância partilhada"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instância de servidor partilhada"
},
"app.modal.install-to-play.view-contents": {
"message": "Ver conteúdo"
},
"app.modal.update-to-play.header": {
"message": "Atualiza para jogar"
},
@@ -197,6 +41,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Opções padrão de instância"
},
"app.settings.tabs.feature-flags": {
"message": "Flags de funcionalidades"
},
"app.settings.tabs.java-installations": {
"message": "Instalações Java"
},
@@ -209,33 +56,6 @@
"app.settings.tabs.resource-management": {
"message": "Gestão de recursos"
},
"app.update-popup.body": {
"message": "Modrinth App v{version} está pronta para ser instalada! Recarrega para atualizar agora, ou automaticamente quando fechares a Modrinth App."
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} acabou de ser transferida. Recarrega para atualizar agora, ou automaticamente quando fechares a Modrinth App."
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} está disponível. Usa o teu gestor de pacotes para atualizar e receber as correções e recursos mais recentes!"
},
"app.update-popup.body.metered": {
"message": "Modrinth App v{version} está disponível! Como estás numa rede com tráfego limitado, não a transferimos automaticamente."
},
"app.update-popup.changelog": {
"message": "Lista de alterações"
},
"app.update-popup.download": {
"message": "Transferir ({size})"
},
"app.update-popup.download-complete": {
"message": "Transferência concluída"
},
"app.update-popup.reload": {
"message": "Recarregar"
},
"app.update-popup.title": {
"message": "Atualização disponível"
},
"app.update.complete-toast.text": {
"message": "Clica aqui para ver a lista de alterações."
},
@@ -251,24 +71,6 @@
"app.update.reload-to-update": {
"message": "Recarrega para instalar a atualização"
},
"app.world.server-modal.placeholder-address": {
"message": "exemplo.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Seleciona uma opção"
},
"app.world.world-item.incompatible-version": {
"message": "Versão incompatível {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Ainda não jogado"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Adicionar um amigo"
},
@@ -368,12 +170,6 @@
"instance.edit-world.title": {
"message": "Editar mundo"
},
"instance.files.adding-files": {
"message": "Adicionando ficheiros ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Guardar como..."
},
"instance.server-modal.address": {
"message": "Endereço"
},
@@ -545,6 +341,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Não mostrar na página Inicial"
},
"instance.worlds.filter.available": {
"message": "Disponível"
},
"instance.worlds.game_already_open": {
"message": "Instância já está aberta"
},
@@ -554,9 +353,6 @@
"instance.worlds.incompatible_server": {
"message": "Servidor é incompatível"
},
"instance.worlds.linked_server": {
"message": "Gerido pelo projeto do servidor"
},
"instance.worlds.no_contact": {
"message": "Servidor não conseguiu ser contactado"
},
@@ -569,6 +365,12 @@
"instance.worlds.play_instance": {
"message": "Jogar instância"
},
"instance.worlds.type.server": {
"message": "Servidor"
},
"instance.worlds.type.singleplayer": {
"message": "Um jogador"
},
"instance.worlds.view_instance": {
"message": "Ver instância"
},
@@ -586,17 +388,5 @@
},
"search.filter.locked.instance.sync": {
"message": "Sincronizar com a instância"
},
"search.filter.locked.server": {
"message": "Fornecido pelo servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Apenas mods no cliente podem ser adicionados à instância de servidor"
},
"search.filter.locked.server-game-version.title": {
"message": "Versão do jogo é fornecida pelo servidor"
},
"search.filter.locked.server-loader.title": {
"message": "Carregador é fornecido pelo servidor"
}
}
@@ -5,6 +5,9 @@
"app.auth-servers.unreachable.header": {
"message": "Nu se pot accesa serverele de autentificare"
},
"app.instance.mods.copy-link": {
"message": "Copiază linkul"
},
"app.modal.install-to-play.content-required": {
"message": "Conținut necesar"
},
@@ -53,6 +56,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Opțiuni implicite instanță"
},
"app.settings.tabs.feature-flags": {
"message": "Controale funcționalitate"
},
"app.settings.tabs.java-installations": {
"message": "Instalări Java"
},
@@ -377,6 +383,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Nu afișa în pagina principală"
},
"instance.worlds.filter.available": {
"message": "Disponibil"
},
"instance.worlds.game_already_open": {
"message": "Instanța este deja deschisă"
},
@@ -401,6 +410,12 @@
"instance.worlds.play_instance": {
"message": "Joacă pe instanța"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Un singur jucător"
},
"instance.worlds.view_instance": {
"message": "Afișează instanța"
},
+45 -114
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Нет связи с серверами аутентификации"
},
"app.browse.add-server-to-instance": {
"message": "Добавить сервер в сборку"
},
"app.browse.add-servers-to-instance": {
"message": "Добавление серверов в сборку"
},
"app.browse.add-to-instance": {
"message": "Добавить в сборку"
},
"app.browse.add-to-instance-name": {
"message": "Добавить в {instanceName}"
},
"app.browse.added": {
"message": "Добавлено"
},
"app.browse.already-added": {
"message": "Уже добавлено"
},
"app.browse.discover-content": {
"message": "Поиск проектов"
},
"app.browse.discover-servers": {
"message": "Поиск серверов"
},
"app.browse.hide-added-servers": {
"message": "Скрывать добавленные"
},
"app.browse.hide-installed-content": {
"message": "Скрывать установленные"
},
"app.browse.install-content-to-instance": {
"message": "Установить контент в сборку"
},
"app.export-modal.description-placeholder": {
"message": "Введите описание сборки..."
},
@@ -45,7 +12,7 @@
"message": "Экспортировать"
},
"app.export-modal.header": {
"message": "Экспорт сборки модов"
"message": "Экспорт модпака"
},
"app.export-modal.modpack-name-label": {
"message": "Название сборки"
@@ -54,7 +21,7 @@
"message": "Название сборки"
},
"app.export-modal.select-files-label": {
"message": "Выберите файлы и папки для экспорта"
"message": "Выберите элементы экспорта в модпак"
},
"app.export-modal.version-number-label": {
"message": "Номер версии"
@@ -74,80 +41,56 @@
"app.instance.confirm-delete.header": {
"message": "Удаление сборки"
},
"app.instance.modpack-already-installed.body": {
"message": "Эта сборка модов уже установлена в сборку <bold>{instanceName}</bold>. Вы уверены, что хотите дублировать её?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Этот модпак уже установлен в инстанции \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Создать"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Дублировать модпак"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Всё равно создать"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "К инстанции"
},
"app.instance.modpack-already-installed.header": {
"message": "Сборка модов уже установлена"
},
"app.instance.modpack-already-installed.instance": {
"message": "Сборка"
"message": "Модпак уже установлен"
},
"app.instance.mods.content-type-project": {
"message": "проект"
},
"app.instance.mods.copy-link": {
"message": "Копировать ссылку"
},
"app.instance.mods.installing": {
"message": "Установка..."
},
"app.instance.mods.modpack-fallback": {
"message": "Сборка"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" был добавлен"
},
"app.instance.mods.projects-were-added": {
"message": "{count} Проектов было добавлено"
"message": "Проектов добавлено: {count}"
},
"app.instance.mods.share-text": {
"message": "Посмотрите проекты, которые я использую в своей сборке модов!"
"message": "Посмотрите проекты, в моём модпаке!"
},
"app.instance.mods.share-title": {
"message": "Содержимое сборки"
},
"app.instance.mods.show-file": {
"message": "Показать в папке"
},
"app.instance.mods.successfully-uploaded": {
"message": "Успешно загружено"
},
"app.instance.worlds.add-server": {
"message": "Добавить сервер"
"app.instance.mods.unknown-version": {
"message": "Неизвестно"
},
"app.instance.worlds.browse-servers": {
"message": "Найти серверы"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' будет **удалён навсегда**, и будет невозможно восстановить."
},
"app.instance.worlds.delete-world-title": {
"message": "Вы уверены, что хотите удалить этот мир навсегда?"
},
"app.instance.worlds.filter-modded": {
"message": "Модовые"
},
"app.instance.worlds.filter-offline": {
"message": "Не в сети"
},
"app.instance.worlds.filter-online": {
"message": "В сети"
},
"app.instance.worlds.filter-vanilla": {
"message": "Ванильные"
},
"app.instance.worlds.no-worlds-description": {
"message": "Добавить сервер или найти, чтобы начать"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Сервера или мира не добавлены"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' будет удален из вашего списка, включая в игре, и восстановить будет невозможно."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) будет удален из вашего списка, включая в игре, и восстановить будет невозможно."
},
"app.instance.worlds.remove-server-title": {
"message": "Вы уверены, что хотите удалить {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Поиск по {count, plural, one {# миру} other {# мирам}}..."
},
"app.instance.worlds.this-server": {
"message": "этот сервер"
"app.instance.mods.updating": {
"message": "Обновление..."
},
"app.modal.install-to-play.content-required": {
"message": "Требуется дополнительный контент"
@@ -174,7 +117,7 @@
"message": "Общая сборка сервера"
},
"app.modal.install-to-play.view-contents": {
"message": "Посмотреть содержимое"
"message": "Посмотреть"
},
"app.modal.update-to-play.header": {
"message": "Обновление перед запуском"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Предустановки"
},
"app.settings.tabs.feature-flags": {
"message": "Флаги функций"
},
"app.settings.tabs.java-installations": {
"message": "Установки Java"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Перезапустить и обновить"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Выберите вариант"
},
"app.world.world-item.incompatible-version": {
"message": "Несовместимая версия {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Ещё не играли"
},
"app.world.world-item.offline": {
"message": "Не в сети"
},
"app.world.world-item.players-online": {
"message": "{count} в игре"
},
"friends.action.add-friend": {
"message": "Добавить в друзья"
},
@@ -330,7 +258,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": "Добавить и играть"
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Настройка мира"
},
"instance.files.adding-files": {
"message": "Добавление файлов ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Сохранить как..."
},
"instance.server-modal.address": {
"message": "Адрес"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Не показывать на главной"
},
"instance.worlds.filter.available": {
"message": "Доступно"
},
"instance.worlds.game_already_open": {
"message": "Сборка уже запущена"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Запустить сборку"
},
"instance.worlds.type.server": {
"message": "Сервер"
},
"instance.worlds.type.singleplayer": {
"message": "Одиночная игра"
},
"instance.worlds.view_instance": {
"message": "Посмотреть сборку"
},
@@ -8,9 +8,24 @@
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.mods.copy-link": {
"message": "Kopiraj link"
},
"app.instance.mods.installing": {
"message": "Instaliranje..."
},
"app.instance.mods.show-file": {
"message": "Prikaži datoteku"
},
"app.instance.mods.successfully-uploaded": {
"message": "Uspešno otpremljeno"
},
"app.instance.mods.unknown-version": {
"message": "Nepoznato"
},
"app.instance.mods.updating": {
"message": "Ažuriranje..."
},
"app.settings.tabs.appearance": {
"message": "Izgled"
},
+31 -112
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Kan ej nå autentiseringsservrarna"
},
"app.browse.add-server-to-instance": {
"message": "Lägg till server till instans"
},
"app.browse.add-servers-to-instance": {
"message": "Lägg till servrar till din instans"
},
"app.browse.add-to-instance": {
"message": "Lägg till i instans"
},
"app.browse.add-to-instance-name": {
"message": "Lägg till i {instanceName}"
},
"app.browse.added": {
"message": "Tillagd"
},
"app.browse.already-added": {
"message": "Redan tillagd"
},
"app.browse.discover-content": {
"message": "Upptäck innehåll"
},
"app.browse.discover-servers": {
"message": "Upptäck servrar"
},
"app.browse.hide-added-servers": {
"message": "Dölj tillagda servrar"
},
"app.browse.hide-installed-content": {
"message": "Dölj installerat innehåll"
},
"app.browse.install-content-to-instance": {
"message": "Installera innehåll till instans"
},
"app.export-modal.description-placeholder": {
"message": "Ange modpaketets beskrivning..."
},
@@ -74,21 +41,21 @@
"app.instance.confirm-delete.header": {
"message": "Radera instans"
},
"app.instance.modpack-already-installed.body": {
"message": "Detta modpaket är redan installerat i instansen <bold>{instanceName}</bold>. Är du säker på att du vill duplicera det?"
},
"app.instance.modpack-already-installed.create": {
"message": "Skapa"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpaket redan installerat"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instans"
"app.instance.modpack-already-installed.create-anyway": {
"message": "Skapa ändå"
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
"app.instance.mods.copy-link": {
"message": "Kopiera länk"
},
"app.instance.mods.installing": {
"message": "Installerar..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpaket"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" lades till"
},
@@ -101,53 +68,17 @@
"app.instance.mods.share-title": {
"message": "Delar innehåll från modpaket"
},
"app.instance.mods.show-file": {
"message": "Visa fil"
},
"app.instance.mods.successfully-uploaded": {
"message": "Uppladdning lyckades"
},
"app.instance.worlds.add-server": {
"message": "Lägg till server"
"app.instance.mods.unknown-version": {
"message": "Okänt"
},
"app.instance.worlds.browse-servers": {
"message": "Utforska servrar"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' kommer ** tas bort permanent**, och det kommer inte finnas något sätt att återskapa den."
},
"app.instance.worlds.delete-world-title": {
"message": "Är du säker på att du vill permanent radera denna värld?"
},
"app.instance.worlds.filter-modded": {
"message": "Moddad"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Lägg till en server eller utforska för att komma igång"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Inga servrar eller världar tillagda"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' kommer bli borttagen från din lista, inklusive i spelet, och det kommer inte finnas något sätt att återskapa den."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) kommer bli borttagen från din lista, inklusive i spelet, och det kommer inte finnas något sätt att återskapa den."
},
"app.instance.worlds.remove-server-title": {
"message": "Är du säker på att du vill radera {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Sök {count} världar..."
},
"app.instance.worlds.this-server": {
"message": "denna server"
"app.instance.mods.updating": {
"message": "Uppdaterar..."
},
"app.modal.install-to-play.content-required": {
"message": "Innehåll krävs"
@@ -197,6 +128,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Standardinställningar för instanser"
},
"app.settings.tabs.feature-flags": {
"message": "Funktionsflaggor"
},
"app.settings.tabs.java-installations": {
"message": "Java-installationer"
},
@@ -231,7 +165,7 @@
"message": "Nedladdning slutförd"
},
"app.update-popup.reload": {
"message": "Ladda om"
"message": "Kolla efter uppdateringar"
},
"app.update-popup.title": {
"message": "Uppdatering tillgänglig"
@@ -251,24 +185,6 @@
"app.update.reload-to-update": {
"message": "Ladda om för att installera uppdatering"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Välj ett alternativ"
},
"app.world.world-item.incompatible-version": {
"message": "Inkompatibel version {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Inte spelad än"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"app.world.world-item.players-online": {
"message": "{count} online"
},
"friends.action.add-friend": {
"message": "Lägg till en vän"
},
@@ -368,12 +284,6 @@
"instance.edit-world.title": {
"message": "Redigera värld"
},
"instance.files.adding-files": {
"message": "Lägger till filer ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Spara som..."
},
"instance.server-modal.address": {
"message": "Adress"
},
@@ -545,6 +455,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Visa inte på Startsidan"
},
"instance.worlds.filter.available": {
"message": "Tillgängliga"
},
"instance.worlds.game_already_open": {
"message": "Instans är redan öppen"
},
@@ -569,6 +482,12 @@
"instance.worlds.play_instance": {
"message": "Spela instans"
},
"instance.worlds.type.server": {
"message": "Server"
},
"instance.worlds.type.singleplayer": {
"message": "Enspelarläge"
},
"instance.worlds.view_instance": {
"message": "Visa instans"
},
+7 -262
View File
@@ -1,142 +1,34 @@
{
"app.auth-servers.unreachable.body": {
"message": "เซิร์ฟเวอร์ตรวจสอบสิทธิ์ของ Minecraft อาจใช้งานไม่ได้ในขณะนี้ โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณและลองใหม่อีกครั้งในภายหลัง"
},
"app.browse.add-server-to-instance": {
"message": "เพิ่มเซิร์ฟเวอร์ลงในอินสแตนซ์"
},
"app.browse.added": {
"message": "เพิ่ม"
},
"app.export-modal.description-placeholder": {
"message": "กรอกคำอธิบายมอดแพ็ก.."
},
"app.export-modal.modpack-name-label": {
"message": "ชื่อมอดแพ็ก"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "ข้อมูลทั้งหมดในอินสแตนซ์ของคุณจะถูกลบอย่างถาวร รวมถึงโลก การตั้งค่า และเนื้อหาที่ติดตั้งไว้ทั้งหมด"
},
"app.instance.confirm-delete.admonition-header": {
"message": "การดำเนินการนี้ไม่สามารถย้อนคืนได้"
},
"app.instance.confirm-delete.delete-button": {
"message": "ลบอินสแตนซ์"
},
"app.instance.modpack-already-installed.body": {
"message": "มอดแพ็กนี้ถูกติดตั้งไว้ในอินสแตนซ์ <bold>{instanceName}</bold> อยู่แล้ว คุณแน่ใจหรือไม่ว่าต้องการสร้างสำเนาซ้ำ?"
},
"app.instance.modpack-already-installed.create": {
"message": "สร้าง"
},
"app.instance.modpack-already-installed.header": {
"message": "ติดตั้งมอดแพ็กนี้อยู่แล้ว"
},
"app.instance.modpack-already-installed.instance": {
"message": "อินสแตนซ์"
},
"app.instance.mods.content-type-project": {
"message": "โปรเจกต์"
},
"app.instance.mods.project-was-added": {
"message": "เพิ่ม \"{name}\" เรียบร้อยแล้ว"
},
"app.instance.mods.projects-were-added": {
"message": "เพิ่ม {count} โปรเจกต์เรียบร้อยแล้ว"
},
"app.instance.mods.share-text": {
"message": "มาตรวจดูโปรเจกต์ต่าง ๆ ที่ฉันใช้ในมอดแพ็กนี้กัน!"
},
"app.instance.mods.successfully-uploaded": {
"message": "อัปโหลดสำเร็จ"
},
"app.instance.worlds.browse-servers": {
"message": "ค้นหาเซิฟเวอร์"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' จะถูกลบอย่างถาวร** และจะไม่สามารถกู้คืนได้**"
},
"app.instance.worlds.delete-world-title": {
"message": "คุณแน่ใจหรือไม่ว่าต้องการลบโลกนี้อย่างถาวร?"
},
"app.instance.worlds.filter-modded": {
"message": "แบบมีมอด"
},
"app.instance.worlds.filter-offline": {
"message": "ออฟไลน์"
},
"app.instance.worlds.filter-online": {
"message": "ออนไลน์"
},
"app.instance.worlds.filter-vanilla": {
"message": "ต้นฉบับ"
},
"app.instance.worlds.no-worlds-description": {
"message": "เพิ่มเซิร์ฟเวอร์หรือสำรวจเพื่อเริ่มต้น"
},
"app.instance.worlds.no-worlds-heading": {
"message": "ไม่มีเซิร์ฟเวอร์หรือโลกที่เพิ่มไว้"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' จะถูกลบออกจากรายชื่อของคุณ รวมถึงในเกมด้วย และจะไม่สามารถกู้คืนได้"
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) จะถูกลบออกจากรายชื่อของคุณ รวมถึงในเกมด้วย และจะไม่สามารถกู้คืนได้"
},
"app.instance.worlds.remove-server-title": {
"message": "คุณแน่ใจหรือไม่ว่าต้องการลบ {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "ค้นหา {count} โลก"
},
"app.instance.worlds.this-server": {
"message": "เซิร์ฟเวอร์อันนี้"
},
"app.modal.install-to-play.content-required": {
"message": "จำเป็นต้องมีเนื้อหา"
},
"app.modal.install-to-play.header": {
"message": "ติดตั้งเพื่อเล่น"
},
"app.modal.install-to-play.install-button": {
"message": "ติดตั้ง"
},
"app.modal.install-to-play.required-modpack": {
"message": "มอดแพ็กที่จำเป็น"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "เซิร์ฟเวอร์นี้จำเป็นต้องใช้มอดเพื่อเข้าเล่น คลิกติดตั้ง เพื่อตั้งค่าไฟล์ที่จำเป็นจาก Modrinth จากนั้นจึงจะเข้าสู่เซิร์ฟเวอร์โดยตรงได้"
},
"app.modal.install-to-play.shared-instance": {
"message": "อินสแตนซ์ที่ใช้ร่วมกัน"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "อินสแตนซ์เซิร์ฟเวอร์ที่ใช้ร่วมกัน"
},
"app.modal.update-to-play.header": {
"message": "อัปเดตเพื่อเล่น"
},
"app.modal.update-to-play.update-required": {
"message": "จำเป็นต้องอัปเดต"
"message": "ต้องมีการอัปเดต"
},
"app.modal.update-to-play.update-required-description": {
"message": "จำเป็นต้องอัปเดตเพื่อเล่น {name} กรุณาอัปเดตเป็นเวอร์ชันล่าสุดเพื่อเปิดเกม"
"message": "ต้องอัปเดตอย่างหนึ่งเพื่อเล่น {name}. โปรดอัปเดตให้เป็นเวอร์ชันล่าสุดเพื่อเปิดเกม"
},
"app.settings.developer-mode-enabled": {
"message": "โหมดนักพัฒนาเปิดอยู่"
"message": "กำลังอยู่ในโหมดผู้พัฒนา"
},
"app.settings.downloading": {
"message": "ดาวน์โหลด เวอร์ชั่น{version}"
},
"app.settings.tabs.appearance": {
"message": "หน้าตา"
"message": "รูปลักษณ์"
},
"app.settings.tabs.default-instance-options": {
"message": "ตัวเลือกอินสแตนซ์เริ่มต้น"
},
"app.settings.tabs.feature-flags": {
"message": "ระบบควบคุมการเปิดใช้งานฟีเจอร์"
},
"app.settings.tabs.java-installations": {
"message": "การจัดการ Java ที่ติดตั้ง"
},
@@ -149,15 +41,6 @@
"app.settings.tabs.resource-management": {
"message": "การจัดการทรัพยากร"
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} พร้อมให้อัปเดตแล้ว โปรดใช้ตัวจัดการแพ็กเกจ ของคุณเพื่ออัปเดตฟีเจอร์ใหม่และตัวแก้ไขล่าสุด!"
},
"app.update-popup.download-complete": {
"message": "ดาวน์โหลดเสร็จสมบูรณ์"
},
"app.update-popup.title": {
"message": "มีการอัปเดตใหม่"
},
"app.update.complete-toast.text": {
"message": "คลิกที่นี่เพื่อดูบันทึกการเปลี่ยนแปลง"
},
@@ -173,18 +56,6 @@
"app.update.reload-to-update": {
"message": "รีโหลดเพื่อติดตั้งอัพเดต"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.world-item.incompatible-version": {
"message": "เวอร์ชัน {version} ไม่รองรับ"
},
"app.world.world-item.offline": {
"message": "ออฟไลน์"
},
"app.world.world-item.players-online": {
"message": "{count} ออนไลน์"
},
"friends.action.add-friend": {
"message": "เพิ่มเพื่อน"
},
@@ -230,9 +101,6 @@
"friends.heading.online": {
"message": "ออฟไลน์"
},
"friends.heading.pending": {
"message": "กําลังดำเนินการ"
},
"friends.no-friends-match": {
"message": "ไม่มีเพื่อนชื่อว่า \"{query}\""
},
@@ -281,12 +149,6 @@
"instance.edit-world.title": {
"message": "แก้ไขโลก"
},
"instance.files.adding-files": {
"message": "กำลังเพิ่มไฟล์ ({completed}/{total})"
},
"instance.files.save-as": {
"message": "บันทึกเป็น..."
},
"instance.server-modal.address": {
"message": "ที่อยู่เซิร์ฟเวอร์"
},
@@ -353,130 +215,13 @@
"instance.settings.tabs.general.name": {
"message": "ชื่อ"
},
"instance.settings.tabs.hooks": {
"message": "ตัวดักการปล่อย"
},
"instance.settings.tabs.hooks.custom-hooks": {
"message": "กำหนดเอง"
},
"instance.settings.tabs.hooks.description": {
"message": "ตัวดักจับช่วยให้ผู้ใช้ระดับสูงสามารถรันคำสั่งระบบบางอย่างได้ ทั้งก่อนและหลังการเปิดเกม"
},
"instance.settings.tabs.hooks.post-exit.enter": {
"message": "ป้อนคำสั่งหลังจบการทำงาน..."
},
"instance.settings.tabs.hooks.pre-launch": {
"message": "ก่อนรัน"
},
"instance.settings.tabs.hooks.title": {
"message": "ฮุกการเปิดเกม"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "ตัวห่อหุ้ม"
},
"instance.settings.tabs.hooks.wrapper.description": {
"message": "คำสั่งตัวห่อหุ้มสำหรับเปิด ไมน์คราฟต์"
},
"instance.settings.tabs.hooks.wrapper.enter": {
"message": "ป้อนตัวห่อหุ้มคำสั่ง..."
},
"instance.settings.tabs.installation": {
"message": "การติดตั้ง"
},
"instance.settings.tabs.installation.loader-version": {
"message": "เวอร์ชั่น {loader}"
},
"instance.settings.tabs.java": {
"message": "Java และ หน่วยความจำ"
},
"instance.settings.tabs.java.hooks": {
"message": "ฮุก"
},
"instance.settings.tabs.java.java-arguments": {
"message": "อาร์กิวเมนต์ Java"
},
"instance.settings.tabs.java.java-installation": {
"message": "การติดตั้ง Java"
},
"instance.settings.tabs.java.java-memory": {
"message": "หน่วยความจำที่จัดสรร"
},
"instance.settings.tabs.window": {
"message": "หน้าต่าง"
},
"instance.settings.tabs.window.fullscreen": {
"message": "เต็มหน้าจอ"
},
"instance.settings.tabs.window.fullscreen.description": {
"message": "ทำให้เกมเปิดในโหมดเต็มหน้าจอเมื่อเริ่มเกม (โดยใช้ไฟล์ options.txt)"
},
"instance.settings.tabs.window.height": {
"message": "ความสูง"
},
"instance.settings.tabs.window.height.description": {
"message": "ความสูงของหน้าต่างเมื่อรันเกม"
},
"instance.settings.tabs.window.height.enter": {
"message": "ป้อนความสูง..."
},
"instance.settings.tabs.window.width": {
"message": "ความกว้าง"
},
"instance.settings.tabs.window.width.description": {
"message": "ความกว้างของหน้าต่างเมื่อรันเกม"
},
"instance.settings.tabs.window.width.enter": {
"message": "ป้อนความกว้าง..."
},
"instance.worlds.a_minecraft_server": {
"message": "เซิร์ฟเวอร์ ไมน์คราฟต์"
},
"instance.worlds.cant_connect": {
"message": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้"
},
"instance.worlds.copy_address": {
"message": "คัดลอกที่อยู่"
},
"instance.worlds.dont_show_on_home": {
"message": "ไม่ต้องแสดงในหน้าแรก"
},
"instance.worlds.game_already_open": {
"message": "อินสแตนซ์ถูกเปิดอยู่แล้ว"
},
"instance.worlds.hardcore": {
"message": "โหมดฮาร์ดคอร์"
},
"instance.worlds.incompatible_server": {
"message": "เซิร์ฟเวอร์ไม่รองรับ"
},
"instance.worlds.linked_server": {
"message": "จัดการโดยโปรเจกต์เซิร์ฟเวอร์"
},
"instance.worlds.no_contact": {
"message": "ไม่สามารถติดต่อเซิร์ฟเวอร์ได้"
},
"instance.worlds.no_server_quick_play": {
"message": "คุณสามารถเข้าสู่เซิร์ฟเวอร์ได้โดยตรงบน Minecraft Alpha 1.0.5 ขึ้นไปเท่านั้น"
},
"instance.worlds.no_singleplayer_quick_play": {
"message": "คุณสามารถเข้าสู่โลกผู้เล่นคนเดียวได้โดยตรงบน Minecraft 1.20 ขึ้นไปเท่านั้น"
},
"instance.worlds.play_instance": {
"message": "เริ่มเล่นอินสแตนซ์"
},
"instance.worlds.view_instance": {
"message": "ดูอินสแตนซ์"
},
"instance.worlds.world_in_use": {
"message": "โลกนี้กำลังถูกใช้งานอยู่"
},
"search.filter.locked.instance": {
"message": "อินสแตนซ์เป็นผู้จัดเตรียม"
},
"search.filter.locked.server": {
"message": "เซิร์ฟเวอร์เป็นผู้จัดเตรียม"
},
"search.filter.locked.server-game-version.title": {
"message": "เซิร์ฟเวอร์เป็นผู้จัดเตรียมเวอร์ชันเกม"
}
}
+31 -124
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Doğrulama sunucularına erişilemedi"
},
"app.browse.add-server-to-instance": {
"message": "Örneğe sunucuyu ekle"
},
"app.browse.add-servers-to-instance": {
"message": "Örneğinize sunucu ekleyin"
},
"app.browse.add-to-instance": {
"message": "Örneğe ekle"
},
"app.browse.add-to-instance-name": {
"message": "{instanceName} örneğine ekle"
},
"app.browse.added": {
"message": "Eklendi"
},
"app.browse.already-added": {
"message": "Zaten eklendi"
},
"app.browse.discover-content": {
"message": "İçerikleri keşfet"
},
"app.browse.discover-servers": {
"message": "Sunucuları keşfet"
},
"app.browse.hide-added-servers": {
"message": "Eklenen sunucuları gizle\n"
},
"app.browse.hide-installed-content": {
"message": "Yüklü İçerikleri Gizle"
},
"app.browse.install-content-to-instance": {
"message": "İçeriği örneğe yükle"
},
"app.export-modal.description-placeholder": {
"message": "Mod paketi açıklaması yazın..."
},
@@ -48,47 +15,38 @@
"message": "Modpaketi çıkart"
},
"app.export-modal.modpack-name-label": {
"message": "Modpaketi Adı"
"message": "Modpaketi adı"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpaketi adı"
},
"app.export-modal.select-files-label": {
"message": "Pakete dahil edilecek dosya ve klasörleri seçin"
},
"app.export-modal.version-number-label": {
"message": "Sürüm numarası"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Dünyalarınız, yapılandırmalarınız ve yüklü tüm içerikler ve tüm veriler kalıcı olarak silinecek."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Bu eylem geri alınamaz"
},
"app.instance.confirm-delete.delete-button": {
"message": "Örneği Sil"
},
"app.instance.confirm-delete.header": {
"message": "Örneği Sil"
},
"app.instance.modpack-already-installed.body": {
"message": "Bu mod paketi zaten <bold>{instanceName}</bold> örneğinde yüklü. Kopyalamak istediğinizden emin misiniz?"
},
"app.instance.modpack-already-installed.create": {
"message": "Oluştur"
"app.instance.modpack-already-installed.create-anyway": {
"message": "Yinede oluştur"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpaketi zaten kurulu"
},
"app.instance.modpack-already-installed.instance": {
"message": "Örnek"
},
"app.instance.mods.content-type-project": {
"message": "proje"
},
"app.instance.mods.copy-link": {
"message": "Bağlantıyı kopyala"
},
"app.instance.mods.installing": {
"message": "Kuruluyor..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpaketi"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" eklendi"
},
@@ -98,56 +56,17 @@
"app.instance.mods.share-text": {
"message": "Modpaketimde kullandığım projelere göz at!"
},
"app.instance.mods.share-title": {
"message": "Mod paketi içeriğini paylaşıyorsunuz"
"app.instance.mods.show-file": {
"message": "Dosya göster"
},
"app.instance.mods.successfully-uploaded": {
"message": "Başarıyla yüklendi"
"message": "Başarı ile yüklendi"
},
"app.instance.worlds.add-server": {
"message": "Sunucu Ekle"
"app.instance.mods.unknown-version": {
"message": "Bilinmeyen"
},
"app.instance.worlds.browse-servers": {
"message": "Sunucuları Keşfet"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' **kalıcı olarak silinecek** ve geri getirilmesi mümkün olmayacaktır."
},
"app.instance.worlds.delete-world-title": {
"message": "Bu dünyayı kalıcı olarak silmek istediğinizden emin misiniz?"
},
"app.instance.worlds.filter-modded": {
"message": "Modlu"
},
"app.instance.worlds.filter-offline": {
"message": "Çevrimdışı"
},
"app.instance.worlds.filter-online": {
"message": "Çevrimiçi"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Başlamak için bir sunucu ekleyin veya göz atın"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Eklenmiş sunucu veya dünya yok"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' oyun içi dahil olmak üzere listenizden kaldırılacak ve geri getirilmesi mümkün olmayacaktır."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) oyun içi dahil olmak üzere listenizden kaldırılacak ve geri getirilmesi mümkün olmayacaktır."
},
"app.instance.worlds.remove-server-title": {
"message": "{name} öğesini kaldırmak istediğinizden emin misiniz?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count} dünya ara..."
},
"app.instance.worlds.this-server": {
"message": "bu sunucu"
"app.instance.mods.updating": {
"message": "Güncelleniyor..."
},
"app.modal.install-to-play.content-required": {
"message": "İçerik gerekli"
@@ -197,6 +116,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Varsayılan profil seçenekleri"
},
"app.settings.tabs.feature-flags": {
"message": "Özellik bayrakları"
},
"app.settings.tabs.java-installations": {
"message": "Java kurulumları"
},
@@ -251,24 +173,6 @@
"app.update.reload-to-update": {
"message": "Güncellemeyi kurmak için yeniden yükleyin"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Bir seçenek seçin"
},
"app.world.world-item.incompatible-version": {
"message": "Uyumsuz sürüm {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Henüz oynanmadı"
},
"app.world.world-item.offline": {
"message": "Çevrimdışı"
},
"app.world.world-item.players-online": {
"message": "{count} çevrimiçi"
},
"friends.action.add-friend": {
"message": "Bir arkadaş ekle"
},
@@ -368,12 +272,6 @@
"instance.edit-world.title": {
"message": "Dünyayı düzenle"
},
"instance.files.adding-files": {
"message": "Dosyalar ekleniyor ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Farklı kaydet..."
},
"instance.server-modal.address": {
"message": "Adres"
},
@@ -545,6 +443,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Ana sayfada gösterme"
},
"instance.worlds.filter.available": {
"message": "Kullanılabilir"
},
"instance.worlds.game_already_open": {
"message": "Bu kurulum zaten açık"
},
@@ -569,6 +470,12 @@
"instance.worlds.play_instance": {
"message": "Kurulumu oyna"
},
"instance.worlds.type.server": {
"message": "Sunucu"
},
"instance.worlds.type.singleplayer": {
"message": "Tek Oyunculu"
},
"instance.worlds.view_instance": {
"message": "Kurulumu gör"
},
+38 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Не вдається зв’язатися зі серверами автентифікації"
},
"app.browse.add-server-to-instance": {
"message": "Додати сервер до профілю"
},
"app.browse.add-servers-to-instance": {
"message": "Додати сервера до вашого профілю"
},
"app.browse.add-to-instance": {
"message": "Додати до профілю"
},
"app.browse.add-to-instance-name": {
"message": "Додати до «{instanceName}»"
},
"app.browse.added": {
"message": "Додано"
},
"app.browse.already-added": {
"message": "Уже додано"
},
"app.browse.discover-content": {
"message": "Дослідити вміст"
},
"app.browse.discover-servers": {
"message": "Дослідити сервера"
},
"app.browse.hide-added-servers": {
"message": "Сховати додані сервери"
},
"app.browse.hide-installed-content": {
"message": "Сховати встановлений уміст"
},
"app.browse.install-content-to-instance": {
"message": "Установити вміст в профіль"
},
"app.export-modal.description-placeholder": {
"message": "Уведіть опис збірки…"
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Видалити профіль"
},
"app.instance.modpack-already-installed.body": {
"message": я збірка вже встановлена в профілі <bold>«{instanceName}»</bold>. Ви впевнені, що хочете її скопіювати?"
"app.instance.modpack-already-installed.admonition-body": {
"message": ю збірку вже встановлено в профілі «{instanceName}»."
},
"app.instance.modpack-already-installed.create": {
"message": "Створити"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Дублікат збірки"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Все одно створити"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Перейти до профілю"
},
"app.instance.modpack-already-installed.header": {
"message": "Збірку вже встановлено"
},
"app.instance.modpack-already-installed.instance": {
"message": "Профіль"
},
"app.instance.mods.content-type-project": {
"message": "проєкт"
},
"app.instance.mods.copy-link": {
"message": "Копіювати посилання"
},
"app.instance.mods.installing": {
"message": "Установлення…"
},
"app.instance.mods.modpack-fallback": {
"message": "Збірка"
},
"app.instance.mods.project-was-added": {
"message": "«{name}» було додано"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Поширення вмісту збірки"
},
"app.instance.mods.show-file": {
"message": "Показати файл"
},
"app.instance.mods.successfully-uploaded": {
"message": "Успішно вивантажено"
},
"app.instance.worlds.add-server": {
"message": "Додати сервер"
"app.instance.mods.unknown-version": {
"message": "Невідомо"
},
"app.instance.worlds.browse-servers": {
"message": "Переглянути сервера"
},
"app.instance.worlds.delete-world-description": {
"message": "«{name}» буде **назавжди видалено** й у вас не буде можливости відновлення."
},
"app.instance.worlds.delete-world-title": {
"message": "Ви впевнені, що хочете назавжди видалити цей світ?"
},
"app.instance.worlds.filter-modded": {
"message": "Модифікований"
},
"app.instance.worlds.filter-offline": {
"message": "Поза мережею"
},
"app.instance.worlds.filter-online": {
"message": "У мережі"
},
"app.instance.worlds.filter-vanilla": {
"message": "Ванільний"
},
"app.instance.worlds.no-worlds-description": {
"message": "Додати сервер або переглянути, щоб почати"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Сервера або світи не додано"
},
"app.instance.worlds.remove-server-description": {
"message": "«{name}» буде видалено з вашого списку, включно в грі, і без можливости відновлення."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "«{name}» ({address}) буде видалено з вашого списку, включно в грі, і без можливости відновлення."
},
"app.instance.worlds.remove-server-title": {
"message": "Ви впевнені, що хочете видалити «{name}»?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Пошук {count} світів…"
},
"app.instance.worlds.this-server": {
"message": "цей сервер"
"app.instance.mods.updating": {
"message": "Оновлення…"
},
"app.modal.install-to-play.content-required": {
"message": "Потрібний уміст"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Усталені налашт. профілю"
},
"app.settings.tabs.feature-flags": {
"message": "Спеціальні функції"
},
"app.settings.tabs.java-installations": {
"message": "Інсталяції Java"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Перезавантажте, щоб установити оновлення"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Виберіть опцію"
},
"app.world.world-item.incompatible-version": {
"message": "Несумісна версія {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Ще не зіграно"
},
"app.world.world-item.offline": {
"message": "Поза мережею"
},
"app.world.world-item.players-online": {
"message": "{count} у мережі"
},
"friends.action.add-friend": {
"message": "Додати друга"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Редагувати світ"
},
"instance.files.adding-files": {
"message": "Додавання файлів ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Зберегти як…"
},
"instance.server-modal.address": {
"message": "Адреса"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Не показувати на головній"
},
"instance.worlds.filter.available": {
"message": "Доступно"
},
"instance.worlds.game_already_open": {
"message": "Профіль вже запущений"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Грати у профіль"
},
"instance.worlds.type.server": {
"message": "Сервер"
},
"instance.worlds.type.singleplayer": {
"message": "Гра наодинці"
},
"instance.worlds.view_instance": {
"message": "Переглянути профіль"
},
+38 -107
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Không thể kết nối đến máy chủ xác thực"
},
"app.browse.add-server-to-instance": {
"message": "Thêm máy chủ vào hồ sơ"
},
"app.browse.add-servers-to-instance": {
"message": "Thêm máy chủ vào hồ sơ của bạn"
},
"app.browse.add-to-instance": {
"message": "Thêm vào hồ sơ"
},
"app.browse.add-to-instance-name": {
"message": "Thêm vào {instanceName}"
},
"app.browse.added": {
"message": "Đã thêm"
},
"app.browse.already-added": {
"message": "Đã được thêm"
},
"app.browse.discover-content": {
"message": "Khám phá nội dung"
},
"app.browse.discover-servers": {
"message": "Khám phá máy chủ"
},
"app.browse.hide-added-servers": {
"message": "Ẩn các máy chủ đã thêm"
},
"app.browse.hide-installed-content": {
"message": "Ẩn các nội dung đã được tải xuống"
},
"app.browse.install-content-to-instance": {
"message": "Tải nội dung này vào hồ sơ"
},
"app.export-modal.description-placeholder": {
"message": "Thêm miêu tả cho gói modpack..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "Xoá hồ sơ"
},
"app.instance.modpack-already-installed.body": {
"message": "Gói modpack này đã được cài đặt trong hồ sơ <bold>{instanceName}</bold> rồi. Bạn có chắc chắn muốn sao chép nó không?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "Modpack này đã được cài đặt trong phiên chơi \"{instanceName}\"."
},
"app.instance.modpack-already-installed.create": {
"message": "Khởi tạo"
"app.instance.modpack-already-installed.admonition-header": {
"message": "Nhân bản modpack"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Vẫn tiếp tục tạo"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "Đi đến phiên chơi"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack đã được cài đặt"
},
"app.instance.modpack-already-installed.instance": {
"message": "Hồ sơ"
},
"app.instance.mods.content-type-project": {
"message": "Dự án"
},
"app.instance.mods.copy-link": {
"message": "Sao chép liên kết"
},
"app.instance.mods.installing": {
"message": "Đang tải xuống..."
},
"app.instance.mods.modpack-fallback": {
"message": "Modpack"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" đã được thêm"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "Chia sẻ hồ sơ modpack"
},
"app.instance.mods.show-file": {
"message": "Hiện tệp"
},
"app.instance.mods.successfully-uploaded": {
"message": "Tải lên thành công"
},
"app.instance.worlds.add-server": {
"message": "Thêm máy chủ"
"app.instance.mods.unknown-version": {
"message": "Không rõ"
},
"app.instance.worlds.browse-servers": {
"message": "Tìm kiếm máy chủ"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' sẽ bị **xóa vĩnh viễn** và không có cách nào khôi phục lại."
},
"app.instance.worlds.delete-world-title": {
"message": "Bạn có chắc cắn rằng bạn muốn xoá vĩnh viễn thế giới này không?"
},
"app.instance.worlds.filter-modded": {
"message": "Modded"
},
"app.instance.worlds.filter-offline": {
"message": "Ngoại tuyến"
},
"app.instance.worlds.filter-online": {
"message": "Trực tuyến"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Thêm một máy chủ hoặc tìm kiếm để bắt đầu"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Không có máy chủ hay thế giới nào đã được thêm"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' sẽ bị xóa khỏi danh sách của bạn, kể cả trong game, và bạn sẽ không có cách nào để khôi phục lại."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) sẽ bị xóa khỏi danh sách của bạn, kể cả trong game, và bạn sẽ không có cách nào để khôi phục lại."
},
"app.instance.worlds.remove-server-title": {
"message": "Bạn có chắc chắn rằng bạn muốn xoá {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Tìm kiếm {count} world..."
},
"app.instance.worlds.this-server": {
"message": "Máy chủ này"
"app.instance.mods.updating": {
"message": "Đang cập nhật..."
},
"app.modal.install-to-play.content-required": {
"message": "Nội dung bắt buộc"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "Tuỳ chọn hồ sơ khởi chạy mặc định"
},
"app.settings.tabs.feature-flags": {
"message": "Các cờ tính năng"
},
"app.settings.tabs.java-installations": {
"message": "Các bản cài Java"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "Hãy khởi động lại để cài đặt bản cập nhật"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Chọn một tuỳ chọn"
},
"app.world.world-item.incompatible-version": {
"message": "Không phù hợp với phiên bản {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Chưa từng được chơi"
},
"app.world.world-item.offline": {
"message": "Ngoại tuyến"
},
"app.world.world-item.players-online": {
"message": "{count} đang trực tuyến"
},
"friends.action.add-friend": {
"message": "Thêm một người bạn"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "Chỉnh sửa thế giới"
},
"instance.files.adding-files": {
"message": "Đang thêm dữ liệu ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Lưu như..."
},
"instance.server-modal.address": {
"message": "Địa chỉ"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "Không hiện ở Trang chủ"
},
"instance.worlds.filter.available": {
"message": "Có sẵn"
},
"instance.worlds.game_already_open": {
"message": "Bản này đã mở sẵn"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "Chơi phiên bản này"
},
"instance.worlds.type.server": {
"message": "Máy chủ"
},
"instance.worlds.type.singleplayer": {
"message": "Chế độ một người chơi"
},
"instance.worlds.view_instance": {
"message": "Xem phiên bản"
},
+41 -110
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "无法连接到身份验证服务器"
},
"app.browse.add-server-to-instance": {
"message": "将服务器添加到实例"
},
"app.browse.add-servers-to-instance": {
"message": "将服务器添加到你的实例"
},
"app.browse.add-to-instance": {
"message": "前往实例"
},
"app.browse.add-to-instance-name": {
"message": "添加到 {instanceName}"
},
"app.browse.added": {
"message": "已添加"
},
"app.browse.already-added": {
"message": "已添加过"
},
"app.browse.discover-content": {
"message": "发现内容"
},
"app.browse.discover-servers": {
"message": "发现服务器"
},
"app.browse.hide-added-servers": {
"message": "隐藏已添加的服务器"
},
"app.browse.hide-installed-content": {
"message": "隐藏已安装的实例"
},
"app.browse.install-content-to-instance": {
"message": "将内容安装到实例中"
},
"app.export-modal.description-placeholder": {
"message": "输入整合包描述……"
},
@@ -74,23 +41,35 @@
"app.instance.confirm-delete.header": {
"message": "删除实例"
},
"app.instance.modpack-already-installed.body": {
"message": "整合包已经安装到实例<bold>{instanceName}</bold> 中了。你确定要重复安装吗?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "这个整合包已经安装在\"{instanceName}\"实例中了。"
},
"app.instance.modpack-already-installed.create": {
"message": "创建"
"app.instance.modpack-already-installed.admonition-header": {
"message": "重复的整合包"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "在任何地方创建"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "前往实例"
},
"app.instance.modpack-already-installed.header": {
"message": "整合包已安装"
},
"app.instance.modpack-already-installed.instance": {
"message": "实例"
},
"app.instance.mods.content-type-project": {
"message": "项目"
},
"app.instance.mods.copy-link": {
"message": "复制链接"
},
"app.instance.mods.installing": {
"message": "正在安装……"
},
"app.instance.mods.modpack-fallback": {
"message": "整合包"
},
"app.instance.mods.project-was-added": {
"message": "已添加 “{name}”"
"message": "已添加“{name}”"
},
"app.instance.mods.projects-were-added": {
"message": "已添加 {count} 个项目"
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "分享整合包内容"
},
"app.instance.mods.show-file": {
"message": "显示文件"
},
"app.instance.mods.successfully-uploaded": {
"message": "上传成功"
},
"app.instance.worlds.add-server": {
"message": "添加服务器"
"app.instance.mods.unknown-version": {
"message": "未知"
},
"app.instance.worlds.browse-servers": {
"message": "浏览服务器"
},
"app.instance.worlds.delete-world-description": {
"message": "“{name}”将被**永久删除**,并且无法恢复。"
},
"app.instance.worlds.delete-world-title": {
"message": "你确定要永久删除这个世界吗?"
},
"app.instance.worlds.filter-modded": {
"message": "模组适配"
},
"app.instance.worlds.filter-offline": {
"message": "离线"
},
"app.instance.worlds.filter-online": {
"message": "在线"
},
"app.instance.worlds.filter-vanilla": {
"message": "原版"
},
"app.instance.worlds.no-worlds-description": {
"message": "添加一个服务器或点击浏览来开始"
},
"app.instance.worlds.no-worlds-heading": {
"message": "未添加服务器或世界"
},
"app.instance.worlds.remove-server-description": {
"message": "将从你的列表中删除“{name}”,包括游戏内,并且无法恢复。"
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "将从你的列表中删除“{name}”({address}),包括游戏内,并且无法恢复。"
},
"app.instance.worlds.remove-server-title": {
"message": "你确定要删除 {name} 吗?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜索 {count} 个世界……"
},
"app.instance.worlds.this-server": {
"message": "此服务器"
"app.instance.mods.updating": {
"message": "更新中……"
},
"app.modal.install-to-play.content-required": {
"message": "内容需求"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "默认实例选项"
},
"app.settings.tabs.feature-flags": {
"message": "功能开关"
},
"app.settings.tabs.java-installations": {
"message": "Java 管理"
},
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "重启以安装更新"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "选择一个选项"
},
"app.world.world-item.incompatible-version": {
"message": "不兼容的版本 {version}"
},
"app.world.world-item.not-played-yet": {
"message": "暂未游玩"
},
"app.world.world-item.offline": {
"message": "离线"
},
"app.world.world-item.players-online": {
"message": "{count} 人在线"
},
"friends.action.add-friend": {
"message": "添加好友"
},
@@ -285,7 +213,7 @@
"message": "这可能与对方的 Minecraft 用户名不同!"
},
"friends.add-friend.username.placeholder": {
"message": "输入 Modrinth 用户名…"
"message": "输入 Modrinth 用户名…"
},
"friends.add-friend.username.title": {
"message": "你好友的 Modrinth 用户名是什么?"
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "编辑存档"
},
"instance.files.adding-files": {
"message": "正在添加文件({completed}/{total}"
},
"instance.files.save-as": {
"message": "保存为……"
},
"instance.server-modal.address": {
"message": "地址"
},
@@ -441,7 +363,7 @@
"message": "名称"
},
"instance.settings.tabs.hooks": {
"message": "启动 Hooks"
"message": "启动Hooks"
},
"instance.settings.tabs.hooks.custom-hooks": {
"message": "自定义启动Hooks"
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "在主界面隐藏"
},
"instance.worlds.filter.available": {
"message": "可用"
},
"instance.worlds.game_already_open": {
"message": "实例已启动"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "游玩实例"
},
"instance.worlds.type.server": {
"message": "服务器"
},
"instance.worlds.type.singleplayer": {
"message": "单人游戏"
},
"instance.worlds.view_instance": {
"message": "查看实例"
},
+39 -108
View File
@@ -5,39 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "無法連線到驗證伺服器"
},
"app.browse.add-server-to-instance": {
"message": "新增此伺服器到實例中"
},
"app.browse.add-servers-to-instance": {
"message": "新增伺服器到實例中"
},
"app.browse.add-to-instance": {
"message": "新增至實例"
},
"app.browse.add-to-instance-name": {
"message": "新增到 {instanceName}"
},
"app.browse.added": {
"message": "已新增"
},
"app.browse.already-added": {
"message": "已新增"
},
"app.browse.discover-content": {
"message": "探索內容"
},
"app.browse.discover-servers": {
"message": "探索伺服器"
},
"app.browse.hide-added-servers": {
"message": "隱藏已新增的伺服器"
},
"app.browse.hide-installed-content": {
"message": "隱藏已安裝的內容"
},
"app.browse.install-content-to-instance": {
"message": "安裝至實例中"
},
"app.export-modal.description-placeholder": {
"message": "輸入模組包描述..."
},
@@ -74,21 +41,33 @@
"app.instance.confirm-delete.header": {
"message": "刪除實例"
},
"app.instance.modpack-already-installed.body": {
"message": "模組包已經安裝在<bold>{instanceName}</bold> 實例中了,您確定要再次安裝嗎?"
"app.instance.modpack-already-installed.admonition-body": {
"message": "這個模組包已經安裝在{instanceName}」實例中了。"
},
"app.instance.modpack-already-installed.create": {
"message": "建立"
"app.instance.modpack-already-installed.admonition-header": {
"message": "複製模組包"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "仍要建立"
},
"app.instance.modpack-already-installed.go-to-instance": {
"message": "前往實例"
},
"app.instance.modpack-already-installed.header": {
"message": "模組包已經安裝"
},
"app.instance.modpack-already-installed.instance": {
"message": "實例"
},
"app.instance.mods.content-type-project": {
"message": "專案"
},
"app.instance.mods.copy-link": {
"message": "複製連結"
},
"app.instance.mods.installing": {
"message": "安裝中..."
},
"app.instance.mods.modpack-fallback": {
"message": "模組包"
},
"app.instance.mods.project-was-added": {
"message": "新增了「{name}」"
},
@@ -101,53 +80,17 @@
"app.instance.mods.share-title": {
"message": "分享模組包內容"
},
"app.instance.mods.show-file": {
"message": "顯示檔案"
},
"app.instance.mods.successfully-uploaded": {
"message": "上傳成功"
},
"app.instance.worlds.add-server": {
"message": "新增伺服器"
"app.instance.mods.unknown-version": {
"message": "未知"
},
"app.instance.worlds.browse-servers": {
"message": "瀏覽伺服器"
},
"app.instance.worlds.delete-world-description": {
"message": "「{name}」將會被**永久刪除**,且無法還原。"
},
"app.instance.worlds.delete-world-title": {
"message": "你確定要永久刪除這個世界嗎?"
},
"app.instance.worlds.filter-modded": {
"message": "模組"
},
"app.instance.worlds.filter-offline": {
"message": "離線"
},
"app.instance.worlds.filter-online": {
"message": "線上"
},
"app.instance.worlds.filter-vanilla": {
"message": "原版"
},
"app.instance.worlds.no-worlds-description": {
"message": "新增伺服器或瀏覽以開始使用"
},
"app.instance.worlds.no-worlds-heading": {
"message": "未新增任何伺服器或世界"
},
"app.instance.worlds.remove-server-description": {
"message": "「{name}」將從你的清單中移除(包含遊戲內),且無法還原。"
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "「{name}」({address}) 將從你的清單中移除(包含遊戲內),且無法還原。"
},
"app.instance.worlds.remove-server-title": {
"message": "你確定要移除「{name}」嗎?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜尋 {count} 個世界..."
},
"app.instance.worlds.this-server": {
"message": "這個伺服器"
"app.instance.mods.updating": {
"message": "更新中..."
},
"app.modal.install-to-play.content-required": {
"message": "所需內容"
@@ -197,6 +140,9 @@
"app.settings.tabs.default-instance-options": {
"message": "預設實例選項"
},
"app.settings.tabs.feature-flags": {
"message": "功能開關"
},
"app.settings.tabs.java-installations": {
"message": "Java 安裝"
},
@@ -216,7 +162,7 @@
"message": "Modrinth App v{version} 已完成下載!立即重新載入以更新,或在關閉 Modrinth App 時自動更新。"
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} 現已推出。請使用軟體包管理員進行更新,以取得最新的功能與修正!"
"message": "Modrinth App v{version} 現已推出。請使用套件管理員進行更新,以取得最新的功能與修正!"
},
"app.update-popup.body.metered": {
"message": "Modrinth App v{version} 現已推出!由於你目前使用的是計量付費網路,我們並未自動下載更新。"
@@ -251,24 +197,6 @@
"app.update.reload-to-update": {
"message": "重新載入即可安裝更新"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "選擇選項"
},
"app.world.world-item.incompatible-version": {
"message": "不相容的版本 {version}"
},
"app.world.world-item.not-played-yet": {
"message": "尚未遊玩"
},
"app.world.world-item.offline": {
"message": "離線"
},
"app.world.world-item.players-online": {
"message": "{count} 人在線上"
},
"friends.action.add-friend": {
"message": "新增好友"
},
@@ -368,12 +296,6 @@
"instance.edit-world.title": {
"message": "編輯世界"
},
"instance.files.adding-files": {
"message": "正在新增檔案 ({completed}/{total})"
},
"instance.files.save-as": {
"message": "另存新檔..."
},
"instance.server-modal.address": {
"message": "位址"
},
@@ -545,6 +467,9 @@
"instance.worlds.dont_show_on_home": {
"message": "不要在首頁顯示"
},
"instance.worlds.filter.available": {
"message": "可用"
},
"instance.worlds.game_already_open": {
"message": "實例已經開啟"
},
@@ -569,6 +494,12 @@
"instance.worlds.play_instance": {
"message": "遊戲實例"
},
"instance.worlds.type.server": {
"message": "伺服器"
},
"instance.worlds.type.singleplayer": {
"message": "單人遊戲"
},
"instance.worlds.view_instance": {
"message": "檢視實例"
},
-3
View File
@@ -1,5 +1,4 @@
import 'floating-vue/dist/style.css'
import 'overlayscrollbars/overlayscrollbars.css'
import * as Sentry from '@sentry/vue'
import { VueScanPlugin } from '@taijased/vue-render-tracker'
@@ -9,7 +8,6 @@ import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from '@/App.vue'
import { overlayScrollbarsDirective } from '@/directives/overlayScrollbars'
import i18nPlugin from '@/plugins/i18n'
import i18nDebugPlugin from '@/plugins/i18n-debug'
import router from '@/routes'
@@ -52,6 +50,5 @@ app.use(FloatingVue, {
})
app.use(i18nPlugin)
app.use(i18nDebugPlugin)
app.directive('overlay-scrollbars', overlayScrollbarsDirective)
app.mount('#app')
File diff suppressed because it is too large Load Diff
-26
View File
@@ -1,26 +0,0 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { injectModrinthClient, ServersManagePageIndex } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'
import { config } from '../config'
const stripePublishableKey = (config.stripePublishableKey as string) || ''
const client = injectModrinthClient()
const { data: products } = useQuery({
queryKey: ['billing', 'products'],
queryFn: () => client.labrinth.billing_internal.getProducts(),
})
const resolvedProducts = computed<Labrinth.Billing.Internal.Product[]>(() => products.value ?? [])
</script>
<template>
<ServersManagePageIndex
:stripe-publishable-key="stripePublishableKey"
:products="resolvedProducts"
/>
</template>
@@ -1,28 +0,0 @@
<script setup lang="ts">
import {
injectModrinthClient,
injectModrinthServerContext,
ServersManageBackupsPage,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
const client = injectModrinthClient()
const { serverId, worldId, isServerRunning } = injectModrinthServerContext()
const queryClient = useQueryClient()
if (worldId.value) {
try {
await queryClient.ensureQueryData({
queryKey: ['backups', 'list', serverId],
queryFn: () => client.archon.backups_v1.list(serverId, worldId.value!),
staleTime: 30_000,
})
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
}
</script>
<template>
<ServersManageBackupsPage :is-server-running="isServerRunning" />
</template>
@@ -1,29 +0,0 @@
<script setup lang="ts">
import {
injectModrinthClient,
injectModrinthServerContext,
ServersManageContentPage,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
const client = injectModrinthClient()
const { serverId, worldId } = injectModrinthServerContext()
const queryClient = useQueryClient()
if (worldId.value) {
try {
await queryClient.ensureQueryData({
queryKey: ['content', 'list', 'v1', serverId],
queryFn: () =>
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
staleTime: 30_000,
})
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
}
</script>
<template>
<ServersManageContentPage />
</template>
@@ -1,26 +0,0 @@
<script setup lang="ts">
import {
injectModrinthClient,
injectModrinthServerContext,
ServersManageFilesPage,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
const client = injectModrinthClient()
const { serverId } = injectModrinthServerContext()
const queryClient = useQueryClient()
try {
await queryClient.ensureQueryData({
queryKey: ['files', serverId, '/'],
queryFn: () => client.kyros.files_v0.listDirectory('/', 1, 2000),
staleTime: 30_000,
})
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
</script>
<template>
<ServersManageFilesPage />
</template>
@@ -1,159 +0,0 @@
<template>
<div class="h-full w-full pt-6">
<ServersManageRootLayout
:server-id="serverId"
:reload-page="() => router.go(0)"
:resolve-viewer="resolveViewer"
:show-copy-id-action="themeStore.devMode"
:auth-user="authUser"
:fetch-intercom-token="fetchIntercomToken"
:navigate-to-billing="() => openUrl('https://modrinth.com/settings/billing')"
:navigate-to-servers="() => router.push('/hosting/manage')"
:browse-modpacks="
({ serverId: sid, worldId: wid, from }) => {
router.push({
path: '/browse/modpack',
query: { sid, wid: wid ?? undefined, from },
})
}
"
:browse-content="
({ serverId: sid, worldId: wid, type }) => {
router.push({
path: `/browse/${type}`,
query: { sid, wid: wid ?? undefined },
})
}
"
>
<template #default="{ onReinstall, onReinstallFailed }">
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense>
<component
:is="Component"
@reinstall="onReinstall"
@reinstall-failed="onReinstallFailed"
/>
</Suspense>
</template>
</RouterView>
</template>
</ServersManageRootLayout>
</div>
</template>
<script setup lang="ts">
import type { Archon, Labrinth } from '@modrinth/api-client'
import { injectAuth, injectModrinthClient, ServersManageRootLayout } from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { config } from '@/config'
import { get_user } from '@/helpers/cache'
import { get as getCreds } from '@/helpers/mr_auth'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useTheming } from '@/store/theme'
const route = useRoute()
const router = useRouter()
const auth = injectAuth()
const client = injectModrinthClient()
const queryClient = useQueryClient()
const themeStore = useTheming()
const breadcrumbs = useBreadcrumbs()
const serverId = computed(() => {
const rawId = route.params.id
return Array.isArray(rawId) ? rawId[0] : (rawId ?? '')
})
if (serverId.value) {
try {
await queryClient.ensureQueryData({
queryKey: ['servers', 'detail', serverId.value],
queryFn: () => client.archon.servers_v0.get(serverId.value)!,
staleTime: 30_000,
})
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
}
const { data: serverData } = useQuery({
queryKey: computed(() => ['servers', 'detail', serverId.value]),
queryFn: () => null as unknown as Archon.Servers.v0.Server,
enabled: false,
})
watch(
serverData,
(server) => {
if (server?.name) {
breadcrumbs.setName('Server', server.name)
breadcrumbs.setContext({
name: server.name,
link: `/hosting/manage/${serverId.value}/content`,
})
}
},
{ immediate: true },
)
watch(
() => auth.user.value,
(user, previousUser) => {
if (user || !previousUser) return
if (route.path === '/hosting/manage' || route.path === '/hosting/manage/') return
void router.replace('/hosting/manage')
},
)
const authUser = computed(() => {
const user = auth.user.value
if (!user?.id) return undefined
return {
id: user.id,
username: user.username,
email: user.email ?? '',
created: user.created,
}
})
async function fetchIntercomToken(): Promise<{ token: string }> {
const credentials = await getCreds()
if (!credentials?.session) {
throw new Error('Not authenticated')
}
const response = await tauriFetch(
`${config.siteUrl}/api/intercom/messenger-jwt?server_id=${encodeURIComponent(serverId.value)}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${credentials.session}`,
},
},
)
if (!response.ok) {
throw new Error(`Failed to fetch Intercom token: ${response.status}`)
}
return (await response.json()) as { token: string }
}
async function resolveViewer(): Promise<{ userId: string | null; userRole: string | null }> {
const credentials = await getCreds().catch(() => null)
if (!credentials?.user_id) {
return { userId: null, userRole: null }
}
const user = await get_user(credentials.user_id, 'bypass').catch(() => null)
const typedUser = user as Labrinth.Users.v2.User | null
return {
userId: credentials.user_id,
userRole: typedUser?.role ?? null,
}
}
</script>
@@ -1,7 +0,0 @@
<script setup lang="ts">
import { ServersManageOverviewPage } from '@modrinth/ui'
</script>
<template>
<ServersManageOverviewPage />
</template>
@@ -1,7 +0,0 @@
import Backups from './Backups.vue'
import Content from './Content.vue'
import Files from './Files.vue'
import Index from './Index.vue'
import Overview from './Overview.vue'
export { Backups, Content, Files, Index, Overview }
+1 -2
View File
@@ -1,7 +1,6 @@
import Browse from './Browse.vue'
import Index from './Index.vue'
import Servers from './Servers.vue'
import Skins from './Skins.vue'
import Worlds from './Worlds.vue'
export { Browse, Index, Servers, Skins, Worlds }
export { Browse, Index, Skins, Worlds }
@@ -1,352 +0,0 @@
<script setup lang="ts">
import type { EditingFile, FileItem, UploadState } from '@modrinth/ui'
import {
commonMessages,
defineMessages,
FilePageLayout,
injectNotificationManager,
provideFileManager,
ReadyTransition,
useDebugLogger,
useVIntl,
} from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core'
import {
mkdir,
readDir,
readFile as readFileBytes,
readTextFile,
remove,
rename,
stat,
writeFile as writeFileBytes,
writeTextFile,
} from '@tauri-apps/plugin-fs'
import { onUnmounted, ref, watch } from 'vue'
import { profile_listener } from '@/helpers/events'
import { get_full_path } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'
import { highlightInFolder } from '@/helpers/utils'
const props = defineProps<{
instance: GameInstance
options: unknown
offline: boolean
playing: boolean
installed: boolean
isServerInstance: boolean
}>()
const { formatMessage } = useVIntl()
const { addNotification } = injectNotificationManager()
const debug = useDebugLogger('Files')
const messages = defineMessages({
saveAs: {
id: 'instance.files.save-as',
defaultMessage: 'Save as...',
},
addingFiles: {
id: 'instance.files.adding-files',
defaultMessage: 'Adding files ({completed}/{total})',
},
})
const instanceRoot = ref('')
const items = ref<FileItem[]>([])
/** True until the first directory read for the current instance path finishes (initial load only). */
const firstPaintPending = ref(true)
const loading = ref(true)
const error = ref<Error | null>(null)
const currentPath = ref('')
const editingFile = ref<EditingFile | null>(null)
debug('setup: start, instance.path =', props.instance.path)
instanceRoot.value = await get_full_path(props.instance.path)
debug('setup: instanceRoot =', instanceRoot.value)
await refresh()
debug('setup: refresh complete, items =', items.value.length, 'error =', error.value)
function resolvePath(relativePath: string): string {
return relativePath ? `${instanceRoot.value}/${relativePath}` : instanceRoot.value
}
async function listDirectory(dirPath: string): Promise<FileItem[]> {
const absPath = resolvePath(dirPath)
debug('listDirectory: dirPath =', dirPath, 'absPath =', absPath)
const entries = await readDir(absPath)
debug('listDirectory: got', entries.length, 'entries')
const results = await Promise.all(
entries.map(async (entry) => {
const entryAbsPath = `${absPath}/${entry.name}`
let metadata
try {
metadata = await stat(entryAbsPath)
} catch {
debug('listDirectory: stat failed for', entry.name, '- skipping')
return null
}
const item: FileItem = {
name: entry.name,
type: entry.isDirectory ? 'directory' : 'file',
path: dirPath ? `${dirPath}/${entry.name}` : entry.name,
modified: metadata.mtime ? Math.floor(metadata.mtime.getTime() / 1000) : 0,
created: metadata.birthtime ? Math.floor(metadata.birthtime.getTime() / 1000) : 0,
}
if (!entry.isDirectory) {
item.size = metadata.size
}
if (entry.isDirectory) {
try {
const children = await readDir(entryAbsPath)
item.count = children.length
} catch {
item.count = 0
}
}
return item
}),
)
return results.filter((item): item is FileItem => item !== null)
}
async function refresh() {
debug('refresh: called, currentPath =', currentPath.value, 'instanceRoot =', instanceRoot.value)
loading.value = true
error.value = null
try {
items.value = await listDirectory(currentPath.value)
debug('refresh: success, items =', items.value.length)
} catch (e) {
debug('refresh: error =', e)
error.value = e instanceof Error ? e : new Error(String(e))
items.value = []
} finally {
loading.value = false
firstPaintPending.value = false
}
}
function navigateTo(path: string) {
debug('navigateTo:', path)
currentPath.value = path.startsWith('/') ? path.slice(1) : path
refresh()
}
function startEditing(file: EditingFile) {
editingFile.value = file
}
function stopEditing() {
editingFile.value = null
}
async function handleCreateItem(name: string, type: 'file' | 'directory') {
const targetPath = currentPath.value ? `${currentPath.value}/${name}` : name
const absPath = resolvePath(targetPath)
try {
if (type === 'directory') {
await mkdir(absPath)
} else {
await writeTextFile(absPath, '')
}
await refresh()
} catch (e) {
addNotification({
title: formatMessage(commonMessages.createFailedLabel),
text: e instanceof Error ? e.message : '',
type: 'error',
})
}
}
async function handleRenameItem(path: string, newName: string) {
const oldAbs = resolvePath(path)
const parentDir = path.includes('/') ? path.substring(0, path.lastIndexOf('/')) : ''
const newPath = parentDir ? `${parentDir}/${newName}` : newName
const newAbs = resolvePath(newPath)
try {
await rename(oldAbs, newAbs)
await refresh()
} catch (e) {
addNotification({
title: formatMessage(commonMessages.renameFailedLabel),
text: e instanceof Error ? e.message : '',
type: 'error',
})
}
}
async function handleMoveItem(source: string, destination: string) {
try {
await rename(resolvePath(source), resolvePath(destination))
await refresh()
} catch (e) {
addNotification({
title: formatMessage(commonMessages.moveFailedLabel),
text: e instanceof Error ? e.message : '',
type: 'error',
})
}
}
async function handleDeleteItem(path: string, recursive: boolean) {
try {
await remove(resolvePath(path), { recursive })
await refresh()
} catch (e) {
addNotification({
title: formatMessage(commonMessages.deleteFailedLabel),
text: e instanceof Error ? e.message : '',
type: 'error',
})
}
}
async function handleReadFile(path: string): Promise<string> {
return await readTextFile(resolvePath(path))
}
async function handleReadFileAsBlob(path: string): Promise<Blob> {
const bytes = await readFileBytes(resolvePath(path))
return new Blob([bytes])
}
async function handleWriteFile(path: string, content: string) {
await writeTextFile(resolvePath(path), content)
}
async function handleDownloadFile(path: string, _fileName: string) {
await invoke('plugin:files|file_save_as', {
instancePath: props.instance.path,
filePath: path,
})
}
const uploadState = ref<UploadState>({
isUploading: false,
currentFileName: null,
currentFileProgress: 0,
uploadedBytes: 0,
totalBytes: 0,
completedFiles: 0,
totalFiles: 0,
})
async function handleUploadFiles(files: File[]) {
if (files.length === 0) return
uploadState.value = {
isUploading: true,
currentFileName: '',
currentFileProgress: 0,
uploadedBytes: 0,
totalBytes: files.reduce((sum, f) => sum + f.size, 0),
completedFiles: 0,
totalFiles: files.length,
}
try {
for (const file of files) {
uploadState.value.currentFileName = file.name
const buffer = await file.arrayBuffer()
const targetPath = resolvePath(
currentPath.value ? `${currentPath.value}/${file.name}` : file.name,
)
await writeFileBytes(targetPath, new Uint8Array(buffer))
uploadState.value.completedFiles++
uploadState.value.uploadedBytes += file.size
uploadState.value.currentFileProgress = 1
}
} catch (e) {
addNotification({
title: formatMessage(commonMessages.uploadFailedLabel),
text: e instanceof Error ? e.message : '',
type: 'error',
})
} finally {
uploadState.value.isUploading = false
await refresh()
}
}
async function handleExtractFile(path: string, override: boolean, dry: boolean) {
try {
return await invoke('plugin:files|file_extract_zip', {
instancePath: props.instance.path,
filePath: path,
overrideConflicts: override,
dryRun: dry,
})
} catch (e) {
addNotification({
title: formatMessage(commonMessages.extractFailedLabel),
text: e instanceof Error ? e.message : '',
type: 'error',
})
}
}
debug('setup: registering profile_listener')
const unlistenProfiles = await profile_listener(
async (event: { event: string; profile_path_id: string }) => {
debug('profile_listener: event =', event.event, 'path =', event.profile_path_id)
if (event.profile_path_id === props.instance.path && event.event === 'synced') {
debug('profile_listener: synced event matched, calling refresh')
await refresh()
}
},
)
debug('setup: profile_listener registered')
onUnmounted(() => {
unlistenProfiles()
})
watch(
() => props.instance.path,
async () => {
debug('watch instance.path: changed to', props.instance.path)
firstPaintPending.value = true
instanceRoot.value = await get_full_path(props.instance.path)
currentPath.value = ''
await refresh()
},
)
provideFileManager({
items,
loading,
error,
currentPath,
navigateTo,
editingFile,
startEditing,
stopEditing,
createItem: handleCreateItem,
renameItem: handleRenameItem,
moveItem: handleMoveItem,
deleteItem: handleDeleteItem,
readFile: handleReadFile,
readFileAsBlob: handleReadFileAsBlob,
writeFile: handleWriteFile,
downloadFile: handleDownloadFile,
uploadFiles: handleUploadFiles,
uploadState,
extractFile: handleExtractFile,
refresh,
basePath: instanceRoot,
openInFolder: (path: string) => highlightInFolder(path),
downloadButtonLabel: formatMessage(messages.saveAs),
uploadingLabel: (completed: number, total: number) =>
formatMessage(messages.addingFiles, { completed, total }),
})
</script>
<template>
<ReadyTransition :pending="firstPaintPending">
<FilePageLayout :show-refresh-button="true" />
</ReadyTransition>
</template>
+43 -45
View File
@@ -1,12 +1,8 @@
<template>
<div v-if="instance" class="flex h-full flex-col">
<div
class="shrink-0 p-6 pr-2 pb-4"
@contextmenu.prevent.stop="(event) => handleRightClick(event)"
>
<div v-if="instance">
<div class="p-6 pr-2 pb-4" @contextmenu.prevent.stop="(event) => handleRightClick(event)">
<ExportModal ref="exportModal" :instance="instance" />
<InstanceSettingsModal
:key="instance.path"
ref="settingsModal"
:instance="instance"
:offline="offline"
@@ -40,6 +36,27 @@
</template>
<template v-else> Never played </template>
</div>
<div v-if="linkedProjectV3" class="w-1.5 h-1.5 rounded-full bg-surface-5"></div>
<div
v-if="linkedProjectV3"
class="flex gap-1.5 items-center font-medium text-primary"
>
Linked to
<Avatar
:src="linkedProjectV3.icon_url"
:alt="linkedProjectV3.name"
:tint-by="instance.path"
size="24px"
/>
<router-link
:to="`/project/${linkedProjectV3.slug ?? linkedProjectV3.id}`"
class="hover:underline text-primary truncate"
>
{{ linkedProjectV3.name }}
</router-link>
</div>
</template>
<template v-else>
@@ -117,9 +134,9 @@
</button>
</ButtonStyled>
<ButtonStyled v-else-if="playing === true" color="red" size="large">
<button :disabled="stopping" @click="stopInstance('InstancePage')">
<button @click="stopInstance('InstancePage')">
<StopCircleIcon />
{{ stopping ? 'Stopping...' : 'Stop' }}
Stop
</button>
</ButtonStyled>
<ButtonStyled
@@ -175,7 +192,7 @@
color="brand"
size="large"
>
<button disabled>Starting...</button>
<button disabled>Loading...</button>
</ButtonStyled>
<ButtonStyled circular size="large">
<button v-tooltip="'Instance settings'" @click="settingsModal?.show()">
@@ -208,17 +225,21 @@
</template>
</ContentPageHeader>
</div>
<div class="shrink-0 px-6">
<div class="px-6">
<NavTabs :links="tabs" />
</div>
<div v-if="!!instance" class="min-h-0 flex-1 overflow-y-auto p-6 pt-4">
<div v-if="!!instance" class="p-6 pt-4">
<RouterView
v-if="route.path.startsWith('/instance')"
v-slot="{ Component }"
:key="instance.path"
>
<template v-if="Component">
<Suspense :key="instance.path">
<Suspense
:key="instance.path"
@pending="loadingBar.startLoading()"
@resolve="loadingBar.stopLoading()"
>
<component
:is="Component"
:instance="instance"
@@ -231,6 +252,9 @@
@play="updatePlayState"
@stop="() => stopInstance('InstanceSubpage')"
></component>
<template #fallback>
<LoadingIndicator />
</template>
</Suspense>
</template>
</RouterView>
@@ -261,7 +285,6 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
BoxesIcon,
CheckCircleIcon,
ClipboardCopyIcon,
DownloadIcon,
@@ -279,7 +302,6 @@ import {
ServerIcon,
SettingsIcon,
StopCircleIcon,
TerminalSquareIcon,
UpdatedIcon,
UserPlusIcon,
XIcon,
@@ -289,14 +311,13 @@ import {
ButtonStyled,
ContentPageHeader,
injectNotificationManager,
NavTabs,
LoadingIndicator,
OverflowMenu,
ServerOnlinePlayers,
ServerPing,
ServerRecentPlays,
ServerRegion,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { convertFileSrc } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
@@ -308,7 +329,7 @@ import ContextMenu from '@/components/ui/ContextMenu.vue'
import ExportModal from '@/components/ui/ExportModal.vue'
import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.vue'
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
import { useInstanceConsole } from '@/composables/useInstanceConsole'
import NavTabs from '@/components/ui/NavTabs.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_v3 } from '@/helpers/cache.js'
import { process_listener, profile_listener } from '@/helpers/events'
@@ -316,17 +337,16 @@ import { get_by_profile_path } from '@/helpers/process'
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'
import { showProfileInFolder } from '@/helpers/utils.js'
import { get_server_status, refreshWorlds } from '@/helpers/worlds'
import { get_server_status } from '@/helpers/worlds'
import { injectServerInstall } from '@/providers/server-install'
import { handleSevereError } from '@/store/error.js'
import { useBreadcrumbs } from '@/store/state'
import { useBreadcrumbs, useLoading } from '@/store/state'
dayjs.extend(duration)
dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const { playServerProject } = injectServerInstall()
const queryClient = useQueryClient()
const route = useRoute()
const router = useRouter()
@@ -343,7 +363,6 @@ window.addEventListener('online', () => {
const instance = ref<GameInstance>()
const playing = ref(false)
const loading = ref(false)
const stopping = ref(false)
const exportModal = ref<InstanceType<typeof ExportModal>>()
const updateToPlayModal = ref<InstanceType<typeof UpdateToPlayModal>>()
@@ -386,14 +405,6 @@ async function fetchInstance() {
}
fetchDeferredData()
if (instance.value) {
queryClient.prefetchQuery({
queryKey: ['worlds', instance.value.path],
queryFn: () => refreshWorlds(instance.value!.path),
staleTime: 30_000,
})
}
}
function fetchDeferredData() {
@@ -440,22 +451,14 @@ const tabs = computed(() => [
{
label: 'Content',
href: `${basePath.value}`,
icon: BoxesIcon,
},
{
label: 'Files',
href: `${basePath.value}/files`,
icon: FolderOpenIcon,
},
{
label: 'Worlds',
href: `${basePath.value}/worlds`,
icon: GlobeIcon,
},
{
label: 'Logs',
href: `${basePath.value}/logs`,
icon: TerminalSquareIcon,
},
])
@@ -473,6 +476,8 @@ if (instance.value) {
})
}
const loadingBar = useLoading()
const options = ref<InstanceType<typeof ContextMenu> | null>(null)
const startInstance = async (context: string) => {
@@ -499,10 +504,8 @@ const startInstance = async (context: string) => {
}
const stopInstance = async (context: string) => {
stopping.value = true
await kill(route.params.id as string).catch(handleError)
stopping.value = false
playing.value = false
await kill(route.params.id as string).catch(handleError)
if (!instance.value) return
trackEvent('InstanceStop', {
@@ -651,11 +654,6 @@ const timePlayedHumanized = computed(() => {
onUnmounted(() => {
unlistenProcesses()
unlistenProfiles()
const profilePath = route.params.id
if (profilePath) {
const { destroy } = useInstanceConsole(profilePath)
destroy()
}
})
</script>
+485 -149
View File
@@ -1,24 +1,127 @@
<template>
<div class="flex flex-col gap-4 h-full">
<ConsolePageLayout />
</div>
<Card class="log-card">
<div class="button-row">
<DropdownSelect
v-model="selectedLogIndex"
:default-value="0"
name="Log date"
:options="logs.map((_, index) => index)"
:display-name="(option) => logs[option]?.name"
:disabled="logs.length === 0"
/>
<div class="button-group">
<Button :disabled="!logs[selectedLogIndex]" @click="copyLog()">
<ClipboardCopyIcon v-if="!copied" />
<CheckIcon v-else />
{{ copied ? 'Copied' : 'Copy' }}
</Button>
<Button color="primary" :disabled="offline || !logs[selectedLogIndex]" @click="share">
<ShareIcon aria-hidden="true" />
Share
</Button>
<Button
v-if="logs[selectedLogIndex] && logs[selectedLogIndex].live === true"
@click="clearLiveLog()"
>
<TrashIcon aria-hidden="true" />
Clear
</Button>
<Button
v-else
:disabled="!logs[selectedLogIndex] || logs[selectedLogIndex].live === true"
color="danger"
@click="deleteLog()"
>
<TrashIcon aria-hidden="true" />
Delete
</Button>
</div>
</div>
<div class="button-row">
<StyledInput
id="text-filter"
v-model="searchFilter"
autocomplete="off"
:icon="SearchIcon"
type="search"
input-class="text-filter"
placeholder="Type to filter logs..."
/>
<div class="filter-group">
<Checkbox
v-for="level in levels"
:key="level.toLowerCase()"
v-model="levelFilters[level.toLowerCase()]"
class="filter-checkbox"
>
{{ level }}
</Checkbox>
</div>
</div>
<div class="log-text">
<RecycleScroller
v-slot="{ item }"
ref="logContainer"
class="scroller"
:items="displayProcessedLogs"
direction="vertical"
:item-size="20"
key-field="id"
buffer="200"
>
<div class="user no-wrap">
<span :style="{ color: item.prefixColor, 'font-weight': item.weight }">{{
item.prefix
}}</span>
<span :style="{ color: item.textColor }">{{ item.text }}</span>
</div>
</RecycleScroller>
</div>
<ShareModalWrapper
ref="shareModal"
header="Share Log"
share-title="Instance Log"
share-text="Check out this log from an instance on the Modrinth App"
:open-in-new-tab="false"
link
/>
</Card>
</template>
<script setup>
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { CheckIcon, ClipboardCopyIcon, SearchIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
import {
ConsolePageLayout,
injectModrinthClient,
Button,
Card,
Checkbox,
DropdownSelect,
injectNotificationManager,
provideConsoleManager,
StyledInput,
} from '@modrinth/ui'
import { computed, onUnmounted, ref, shallowRef, triggerRef, watch, watchEffect } from 'vue'
import dayjs from 'dayjs'
import isToday from 'dayjs/plugin/isToday'
import isYesterday from 'dayjs/plugin/isYesterday'
import { ofetch } from 'ofetch'
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { RecycleScroller } from 'vue-virtual-scroller'
import { useInstanceConsole } from '@/composables/useInstanceConsole'
import { log_listener, process_listener } from '@/helpers/events.js'
import { delete_logs_by_filename, get_output_by_filename } from '@/helpers/logs.js'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
import { process_listener } from '@/helpers/events.js'
import {
delete_logs_by_filename,
get_latest_log_cursor,
get_logs,
get_output_by_filename,
} from '@/helpers/logs.js'
import { get_by_profile_path } from '@/helpers/process.js'
dayjs.extend(isToday)
dayjs.extend(isYesterday)
const client = injectModrinthClient()
const { handleError } = injectNotificationManager()
const route = useRoute()
@@ -55,181 +158,414 @@ const props = defineProps({
},
})
const profilePathId = computed(() => route.params.id)
const {
liveConsole,
historicalConsole,
hydrate,
getHistoricalLogs,
getHistoricalContent,
invalidate,
clearLive,
} = useInstanceConsole(profilePathId.value)
const currentLiveLog = ref(null)
const currentLiveLogCursor = ref(0)
const emptyText = ['No live game detected.', 'Start your game to proceed.']
await hydrate()
const logs = ref([])
await setLogs()
function buildLogList(rawLogs) {
return [
{ name: 'Live Log', live: true },
...rawLogs
.filter(
(log) =>
log.filename !== 'latest_stdout.log' &&
log.filename !== 'latest_stdout' &&
log.filename !== 'launcher_log.txt' &&
log.stdout !== '' &&
(log.filename.includes('.log') || log.filename.endsWith('.txt')),
)
.map((log) => ({
...log,
name: log.filename || 'Unknown',
})),
]
}
const logs = ref(buildLogList([]))
void getHistoricalLogs(props.instance.path)
.then((allLogs) => {
logs.value = buildLogList(allLogs)
})
.catch(handleError)
const logsColored = true
const selectedLogIndex = ref(0)
const isLive = computed(() => selectedLogIndex.value === 0)
const copied = ref(false)
const logContainer = ref(null)
const interval = ref(null)
const userScrolled = ref(false)
const isAutoScrolling = ref(false)
const shareModal = ref(null)
const filteredLogs = computed(() =>
props.playing ? logs.value.filter((l) => l.live || l.name !== 'latest.log') : logs.value,
)
const levels = ['Comment', 'Error', 'Warn', 'Info', 'Debug', 'Trace']
const levelFilters = ref({})
levels.forEach((level) => {
levelFilters.value[level.toLowerCase()] = true
})
const searchFilter = ref('')
const logSources = computed(() =>
filteredLogs.value.map((l, i) => ({
id: String(i),
name: l?.name ?? `Log ${i}`,
live: l?.live ?? false,
})),
)
function shouldDisplay(processedLine) {
if (!processedLine.level) {
return true
}
const activeConsole = computed(() => (isLive.value ? liveConsole : historicalConsole))
if (!levelFilters.value[processedLine.level.toLowerCase()]) {
return false
}
if (searchFilter.value !== '') {
if (!processedLine.text.toLowerCase().includes(searchFilter.value.toLowerCase())) {
return false
}
}
return true
}
const logLines = shallowRef(activeConsole.value.output.value)
watchEffect(() => {
logLines.value = activeConsole.value.output.value
triggerRef(logLines)
// Selects from the processed logs which ones should be displayed (shouldDisplay)
// In addition, splits each line by \n. Each split line is given the same properties as the original line
const displayProcessedLogs = computed(() => {
return processedLogs.value.filter((l) => shouldDisplay(l))
})
const crashAnalysis = ref(null)
const processedLogs = computed(() => {
// split based on newline and timestamp lookahead
// (not just newline because of multiline messages)
const splitPattern = /\n(?=(?:#|\[\d\d:\d\d:\d\d\]))/
async function analyseForCrash() {
const lines = liveConsole.output.value
if (lines.length === 0) return
const lines = logs.value[selectedLogIndex.value]?.stdout.split(splitPattern) || []
const processed = []
let id = 0
for (let i = 0; i < lines.length; i++) {
// Then split off of \n.
// Lines that are not the first have prefix = null
const text = getLineText(lines[i])
const prefix = getLinePrefix(lines[i])
const prefixColor = getLineColor(lines[i], true)
const textColor = getLineColor(lines[i], false)
const weight = getLineWeight(lines[i])
const level = getLineLevel(lines[i])
text.split('\n').forEach((line, index) => {
processed.push({
id: id,
text: line,
prefix: index === 0 ? prefix : null,
prefixColor: prefixColor,
textColor: textColor,
weight: weight,
level: level,
})
id += 1
})
}
return processed
})
const content = lines.map((l) => l.text).join('\n')
try {
const data = await client.mclogs.insights_v1.analyse(content)
if (data.analysis?.problems?.length > 0) {
crashAnalysis.value = data
async function getLiveStdLog() {
if (route.params.id) {
const processes = await get_by_profile_path(route.params.id).catch(handleError)
let returnValue
if (processes.length === 0) {
returnValue = emptyText.join('\n')
} else {
const logCursor = await get_latest_log_cursor(
props.instance.path,
currentLiveLogCursor.value,
).catch(handleError)
if (logCursor.new_file) {
currentLiveLog.value = ''
}
currentLiveLog.value = currentLiveLog.value + logCursor.output
currentLiveLogCursor.value = logCursor.cursor
returnValue = currentLiveLog.value
}
} catch {
// Crash analysis is best-effort
return { name: 'Live Log', stdout: returnValue, live: true }
}
return null
}
async function getLogs() {
return (await get_logs(props.instance.path, true).catch(handleError))
.filter(
// filter out latest_stdout.log or anything without .log in it
(log) =>
log.filename !== 'latest_stdout.log' &&
log.filename !== 'latest_stdout' &&
log.stdout !== '' &&
(log.filename.includes('.log') || log.filename.endsWith('.txt')),
)
.map((log) => {
log.name = log.filename || 'Unknown'
log.stdout = 'Loading...'
return log
})
}
async function setLogs() {
const [liveStd, allLogs] = await Promise.all([getLiveStdLog(), getLogs()])
logs.value = [liveStd, ...allLogs]
}
const copyLog = () => {
if (logs.value.length > 0 && logs.value[selectedLogIndex.value]) {
navigator.clipboard.writeText(logs.value[selectedLogIndex.value].stdout)
copied.value = true
}
}
const selectedLog = computed(() => filteredLogs.value[selectedLogIndex.value])
const share = async () => {
if (logs.value.length > 0 && logs.value[selectedLogIndex.value]) {
const url = await ofetch('https://api.mclo.gs/1/log', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `content=${encodeURIComponent(logs.value[selectedLogIndex.value].stdout)}`,
}).catch(handleError)
const deleteDisabled = computed(() => {
const log = selectedLog.value
if (!log || log.live) return true
return log.filename === 'latest.log' && props.playing
shareModal.value.show(url.url)
}
}
watch(selectedLogIndex, async (newIndex) => {
copied.value = false
userScrolled.value = false
if (logs.value.length > 1 && newIndex !== 0) {
logs.value[newIndex].stdout = 'Loading...'
logs.value[newIndex].stdout = await get_output_by_filename(
props.instance.path,
logs.value[newIndex].log_type,
logs.value[newIndex].filename,
).catch(handleError)
}
})
async function deleteSelectedLog() {
const log = selectedLog.value
if (!log || log.live) return
await delete_logs_by_filename(props.instance.path, log.log_type, log.filename)
invalidate()
const freshLogs = await getHistoricalLogs(props.instance.path)
logs.value = buildLogList(freshLogs)
if (logs.value.length > 1 && !props.playing) {
selectedLogIndex.value = 1
} else {
selectedLogIndex.value = 0
}
provideConsoleManager({
logLines,
logSources,
activeLogSourceIndex: selectedLogIndex,
showCommandInput: false,
loading: ref(false),
onClear: () => {
if (!isLive.value) return
void clearLive()
},
onDelete: deleteSelectedLog,
deleteDisabled,
deleteDisabledTooltip: 'Cannot delete latest.log while the instance is running',
shareDisabled: computed(() => props.offline),
emptyStateType: 'instance',
crashAnalysis,
onDismissCrash: () => {
crashAnalysis.value = null
},
})
watch(selectedLogIndex, async (newIndex) => {
if (newIndex === 0) return
const log = filteredLogs.value[newIndex]
if (!log) return
const cached = getHistoricalContent(log.filename)
if (cached) {
historicalConsole.clear()
historicalConsole.addLegacyLog(cached)
return
const deleteLog = async () => {
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
const deleteIndex = selectedLogIndex.value
selectedLogIndex.value = deleteIndex - 1
await delete_logs_by_filename(
props.instance.path,
logs.value[deleteIndex].log_type,
logs.value[deleteIndex].filename,
).catch(handleError)
await setLogs()
}
const output = await get_output_by_filename(
props.instance.path,
log.log_type,
log.filename,
).catch(handleError)
if (output) {
historicalConsole.clear()
historicalConsole.addLegacyLog(output)
}
})
selectedLogIndex.value = 0
if (!props.playing) {
void analyseForCrash()
}
const unlistenLog = await log_listener((payload) => {
if (payload.profile_path_id !== profilePathId.value) return
const clearLiveLog = async () => {
currentLiveLog.value = ''
// does not reset cursor
}
if (payload.type === 'log4j') {
liveConsole.addLog4jEvent(payload)
} else if (payload.type === 'legacy') {
liveConsole.addLegacyLog(payload.message)
const isLineLevel = (text, level) => {
if ((text.includes('/INFO') || text.includes('[System] [CHAT]')) && level === 'info') {
return true
}
})
if (text.includes('/WARN') && level === 'warn') {
return true
}
if (text.includes('/DEBUG') && level === 'debug') {
return true
}
if (text.includes('/TRACE') && level === 'trace') {
return true
}
const errorTriggers = ['/ERROR', 'Exception:', ':?]', 'Error', '[thread', ' at']
if (level === 'error') {
for (const trigger of errorTriggers) {
if (text.includes(trigger)) return true
}
}
if (text.trim()[0] === '#' && level === 'comment') {
return true
}
return false
}
const getLineWeight = (text) => {
if (
!logsColored ||
isLineLevel(text, 'info') ||
isLineLevel(text, 'debug') ||
isLineLevel(text, 'trace')
) {
return 'normal'
}
if (isLineLevel(text, 'error') || isLineLevel(text, 'warn')) {
return 'bold'
}
}
const getLineLevel = (text) => {
for (const level of levels) {
if (isLineLevel(text, level.toLowerCase())) {
return level
}
}
}
const getLineColor = (text, prefix) => {
if (isLineLevel(text, 'comment')) {
return 'var(--color-green)'
}
if (!logsColored || text.includes('[System] [CHAT]')) {
return 'var(--color-white)'
}
if (
(isLineLevel(text, 'info') || isLineLevel(text, 'debug') || isLineLevel(text, 'trace')) &&
prefix
) {
return 'var(--color-blue)'
}
if (isLineLevel(text, 'warn')) {
return 'var(--color-orange)'
}
if (isLineLevel(text, 'error')) {
return 'var(--color-red)'
}
}
const getLinePrefix = (text) => {
if (text.includes(']:')) {
return text.split(']:')[0] + ']:'
}
}
const getLineText = (text) => {
if (text.includes(']:')) {
if (text.split(']:').length > 2) {
return text.split(']:').slice(1).join(']:')
}
return text.split(']:')[1]
} else {
return text
}
}
function handleUserScroll() {
if (!isAutoScrolling.value) {
userScrolled.value = true
}
}
interval.value = setInterval(async () => {
if (logs.value.length > 0) {
logs.value[0] = await getLiveStdLog()
const scroll = logContainer.value.getScroll()
// Allow resetting of userScrolled if the user scrolls to the bottom
if (selectedLogIndex.value === 0) {
if (scroll.end >= logContainer.value.$el.scrollHeight - 10) userScrolled.value = false
if (!userScrolled.value) {
await nextTick()
isAutoScrolling.value = true
logContainer.value.scrollToItem(displayProcessedLogs.value.length - 1)
setTimeout(() => (isAutoScrolling.value = false), 50)
}
}
}
}, 250)
const unlistenProcesses = await process_listener(async (e) => {
if (e.profile_path_id !== profilePathId.value) return
if (e.event === 'launched') {
liveConsole.clear()
invalidate()
currentLiveLog.value = ''
currentLiveLogCursor.value = 0
selectedLogIndex.value = 0
}
if (e.event === 'finished') {
invalidate()
const freshLogs = await getHistoricalLogs(props.instance.path)
logs.value = buildLogList(freshLogs)
void analyseForCrash()
currentLiveLog.value = ''
currentLiveLogCursor.value = 0
userScrolled.value = false
await setLogs()
selectedLogIndex.value = 1
}
})
onMounted(() => {
logContainer.value.$el.addEventListener('scroll', handleUserScroll)
})
onBeforeUnmount(() => {
logContainer.value.$el.removeEventListener('scroll', handleUserScroll)
})
onUnmounted(() => {
unlistenLog()
clearInterval(interval.value)
unlistenProcesses()
})
</script>
<style scoped lang="scss">
.log-card {
display: flex;
flex-direction: column;
gap: 1rem;
height: 100vh;
}
.button-row {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 0.5rem;
}
.button-group {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.log-text {
width: 100%;
height: 100%;
font-family: var(--mono-font);
background-color: var(--color-accent-contrast);
color: var(--color-contrast);
border-radius: var(--radius-lg);
padding-top: 1.5rem;
overflow-x: auto; /* Enables horizontal scrolling */
overflow-y: hidden; /* Disables vertical scrolling on this wrapper */
white-space: nowrap; /* Keeps content on a single line */
white-space: normal;
color-scheme: dark;
}
.filter-checkbox {
margin-bottom: 0.3rem;
font-size: 1rem;
svg {
display: flex;
align-self: center;
justify-self: center;
}
}
.filter-group {
display: flex;
padding: 0.6rem;
flex-direction: row;
overflow: auto;
gap: 0.5rem;
&::-webkit-scrollbar-track,
&::-webkit-scrollbar-thumb {
border-radius: 10px;
}
}
:deep(.vue-recycle-scroller__item-wrapper) {
overflow: visible; /* Enables horizontal scrolling */
}
:deep(.vue-recycle-scroller) {
&::-webkit-scrollbar-corner {
background-color: var(--color-bg);
border-radius: 0 0 10px 0;
}
}
.scroller {
height: 100%;
}
.user {
height: 32%;
padding: 0 1.5rem;
display: flex;
align-items: center;
user-select: text;
}
</style>
+126 -106
View File
@@ -1,76 +1,71 @@
<template>
<ReadyTransition :pending="loading">
<ContentPageLayout>
<template #modals>
<ShareModalWrapper
ref="shareModal"
:share-title="formatMessage(messages.shareTitle)"
:share-text="formatMessage(messages.shareText)"
:open-in-new-tab="false"
/>
<ModpackContentModal
ref="modpackContentModal"
:modpack-name="linkedModpackProject?.title"
:modpack-icon-url="linkedModpackProject?.icon_url ?? undefined"
:enable-toggle="!props.isServerInstance"
:get-overflow-options="getOverflowOptions"
:switch-version="handleSwitchVersion"
@update:enabled="handleModpackContentToggle"
@bulk:enable="handleModpackContentBulkToggle"
@bulk:disable="handleModpackContentBulkToggle"
/>
<ConfirmModpackUpdateModal
ref="modpackUpdateConfirmModal"
:downgrade="isModpackUpdateDowngrade"
:backup-tip="
[linkedModpackProject?.title, pendingModpackUpdateVersion?.version_number]
.filter(Boolean)
.join(' ')
"
@confirm="handleModpackUpdateConfirm"
@cancel="handleModpackUpdateCancel"
/>
<ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" />
<ContentUpdaterModal
v-if="updatingProject || updatingModpack"
ref="contentUpdaterModal"
:versions="updatingProjectVersions"
:current-game-version="instance.game_version"
:current-loader="instance.loader"
:current-version-id="
updatingModpack
? (instance.linked_data?.version_id ?? '')
: (updatingProject?.version?.id ?? '')
"
:is-app="true"
:project-type="updatingModpack ? 'modpack' : updatingProject?.project_type"
:project-icon-url="
updatingModpack ? linkedModpackProject?.icon_url : updatingProject?.project?.icon_url
"
:project-name="
updatingModpack
? (linkedModpackProject?.title ?? formatMessage(commonMessages.modpackLabel))
: (updatingProject?.project?.title ?? updatingProject?.file_name)
"
:loading="loadingVersions"
:loading-changelog="loadingChangelog"
@update="handleModalUpdate"
@cancel="resetUpdateState"
@version-select="handleVersionSelect"
@version-hover="handleVersionHover"
/>
</template>
</ContentPageLayout>
</ReadyTransition>
<ContentPageLayout>
<template #modals>
<ShareModalWrapper
ref="shareModal"
:share-title="formatMessage(messages.shareTitle)"
:share-text="formatMessage(messages.shareText)"
:open-in-new-tab="false"
/>
<ModpackContentModal
ref="modpackContentModal"
:modpack-name="linkedModpackProject?.title"
:modpack-icon-url="linkedModpackProject?.icon_url ?? undefined"
:enable-toggle="!props.isServerInstance"
:get-overflow-options="getOverflowOptions"
@update:enabled="handleModpackContentToggle"
@bulk:enable="handleModpackContentBulkToggle"
@bulk:disable="handleModpackContentBulkToggle"
/>
<ConfirmModpackUpdateModal
ref="modpackUpdateConfirmModal"
:downgrade="isModpackUpdateDowngrade"
:backup-tip="
[linkedModpackProject?.title, pendingModpackUpdateVersion?.version_number]
.filter(Boolean)
.join(' ')
"
@confirm="handleModpackUpdateConfirm"
@cancel="handleModpackUpdateCancel"
/>
<ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" />
<ContentUpdaterModal
v-if="updatingProject || updatingModpack"
ref="contentUpdaterModal"
:versions="updatingProjectVersions"
:current-game-version="instance.game_version"
:current-loader="instance.loader"
:current-version-id="
updatingModpack
? (instance.linked_data?.version_id ?? '')
: (updatingProject?.version?.id ?? '')
"
:is-app="true"
:project-type="updatingModpack ? 'modpack' : updatingProject?.project_type"
:project-icon-url="
updatingModpack ? linkedModpackProject?.icon_url : updatingProject?.project?.icon_url
"
:project-name="
updatingModpack
? (linkedModpackProject?.title ?? formatMessage(messages.modpackFallback))
: (updatingProject?.project?.title ?? updatingProject?.file_name)
"
:loading="loadingVersions"
:loading-changelog="loadingChangelog"
@update="handleModalUpdate"
@cancel="resetUpdateState"
@version-select="handleVersionSelect"
@version-hover="handleVersionHover"
/>
</template>
</ContentPageLayout>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { ClipboardCopyIcon, FolderOpenIcon } from '@modrinth/assets'
import {
commonMessages,
ConfirmModpackUpdateModal,
ContentCardLayout as ContentPageLayout,
type ContentItem,
type ContentModpackCardCategory,
type ContentModpackCardProject,
@@ -84,10 +79,10 @@ import {
type OverflowMenuOption,
provideAppBackup,
provideContentManager,
ReadyTransition,
useDebugLogger,
useVIntl,
} from '@modrinth/ui'
import { ContentCardLayout as ContentPageLayout } from '@modrinth/ui'
import { getCurrentWebview } from '@tauri-apps/api/webview'
import { open } from '@tauri-apps/plugin-dialog'
import { openUrl } from '@tauri-apps/plugin-opener'
@@ -130,6 +125,10 @@ const messages = defineMessages({
id: 'app.instance.mods.share-text',
defaultMessage: "Check out the projects I'm using in my modpack!",
},
modpackFallback: {
id: 'app.instance.mods.modpack-fallback',
defaultMessage: 'Modpack',
},
successfullyUploaded: {
id: 'app.instance.mods.successfully-uploaded',
defaultMessage: 'Successfully uploaded',
@@ -142,10 +141,30 @@ const messages = defineMessages({
id: 'app.instance.mods.projects-were-added',
defaultMessage: '{count} projects were added',
},
updating: {
id: 'app.instance.mods.updating',
defaultMessage: 'Updating...',
},
installing: {
id: 'app.instance.mods.installing',
defaultMessage: 'Installing...',
},
contentTypeProject: {
id: 'app.instance.mods.content-type-project',
defaultMessage: 'project',
},
unknownVersion: {
id: 'app.instance.mods.unknown-version',
defaultMessage: 'Unknown',
},
showFile: {
id: 'app.instance.mods.show-file',
defaultMessage: 'Show file',
},
copyLink: {
id: 'app.instance.mods.copy-link',
defaultMessage: 'Copy link',
},
})
let savedModalState: ModpackContentModalState | null = null
@@ -264,7 +283,7 @@ async function handleUploadFiles() {
}
}
async function toggleDisableMod(mod: ContentItem) {
async function _toggleDisableMod(mod: ContentItem) {
try {
mod.file_path = await toggle_disable_project(props.instance.path, mod.file_path!)
mod.enabled = !mod.enabled
@@ -282,7 +301,7 @@ async function toggleDisableMod(mod: ContentItem) {
}
}
const toggleDisableDebounced = useDebounceFn(toggleDisableMod, 20)
const toggleDisableMod = useDebounceFn(_toggleDisableMod, 20)
async function removeMod(mod: ContentItem) {
await remove_project(props.instance.path, mod.file_path!).catch(handleError)
@@ -335,12 +354,6 @@ async function updateProject(mod: ContentItem) {
}
async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions.v2.Version) {
isBulkOperating.value = true
mod.installing = true
if (mod.version) {
mod.version.id = version.id
mod.version.version_number = version.version_number
}
try {
await remove_project(props.instance.path, mod.file_path!)
const newPath = await add_project_from_version(props.instance.path, version.id)
@@ -351,12 +364,20 @@ async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions
}
mod.file_path = newPath
if (mod.version) {
mod.version.id = version.id
mod.version.version_number = version.version_number
}
trackEvent('InstanceProjectSwitchVersion', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
})
} catch (err) {
handleError(err as Error)
} finally {
mod.installing = false
isBulkOperating.value = false
await initProjects()
}
}
@@ -447,11 +468,11 @@ async function handleSwitchVersion(item: ContentItem) {
}
async function handleModpackContentToggle(item: ContentItem) {
await toggleDisableDebounced(item)
await toggleDisableMod(item)
}
async function handleModpackContentBulkToggle(items: ContentItem[]) {
await Promise.all(items.map((item) => toggleDisableMod(item)))
await Promise.all(items.map((item) => _toggleDisableMod(item)))
}
async function handleModpackContent() {
@@ -518,7 +539,7 @@ async function fetchAndSpliceVersion(
async function handleVersionSelect(version: Labrinth.Versions.v2.Version) {
if (version.changelog != null) return
loadingChangelog.value = true
await fetchAndSpliceVersion(version.id, 'must_revalidate', handleError as (err: unknown) => void)
await fetchAndSpliceVersion(version.id, 'must_revalidate', handleError)
loadingChangelog.value = false
}
@@ -639,14 +660,14 @@ function getOverflowOptions(item: ContentItem): OverflowMenuOption[] {
const options: OverflowMenuOption[] = []
options.push({
id: formatMessage(commonMessages.showFileButton),
id: formatMessage(messages.showFile),
icon: FolderOpenIcon,
action: () => highlightModInProfile(props.instance.path, item.file_path),
})
if (item.project?.slug) {
options.push({
id: formatMessage(commonMessages.copyLinkButton),
id: formatMessage(messages.copyLink),
icon: ClipboardCopyIcon,
action: async () => {
await navigator.clipboard.writeText(
@@ -697,13 +718,18 @@ async function initProjects(cacheBehaviour?: CacheBehaviour) {
if (allCategories && modpackInfo.project.categories) {
const seen = new Set<string>()
linkedModpackCategories.value = allCategories.filter((cat: { name: string }) => {
if (modpackInfo.project.categories.includes(cat.name) && !seen.has(cat.name)) {
seen.add(cat.name)
return true
}
return false
})
linkedModpackCategories.value = allCategories
.filter((cat: { name: string }) => {
if (modpackInfo.project.categories.includes(cat.name) && !seen.has(cat.name)) {
seen.add(cat.name)
return true
}
return false
})
.map((cat: { name: string }) => ({
...cat,
name: cat.name.charAt(0).toUpperCase() + cat.name.slice(1),
}))
} else {
linkedModpackCategories.value = []
}
@@ -773,8 +799,8 @@ provideContentManager({
hasUpdate: linkedModpackHasUpdate.value,
disabled: isModpackUpdating.value,
disabledText: isModpackUpdating.value
? formatMessage(commonMessages.updatingLabel)
: formatMessage(commonMessages.installingLabel),
? formatMessage(messages.updating)
: formatMessage(messages.installing),
}
: null,
),
@@ -782,18 +808,13 @@ provideContentManager({
isBusy: isInstanceBusy,
isBulkOperating,
contentTypeLabel: ref(formatMessage(messages.contentTypeProject)),
toggleEnabled: toggleDisableDebounced,
bulkEnableItems: (items: ContentItem[]) =>
Promise.all(items.filter((item) => !item.enabled).map((item) => toggleDisableMod(item))).then(
() => {},
),
bulkDisableItems: (items: ContentItem[]) =>
Promise.all(items.filter((item) => item.enabled).map((item) => toggleDisableMod(item))).then(
() => {},
),
toggleEnabled: toggleDisableMod,
bulkEnableItems: (items) =>
Promise.all(items.map((item) => _toggleDisableMod(item))).then(() => {}),
bulkDisableItems: (items) =>
Promise.all(items.map((item) => _toggleDisableMod(item))).then(() => {}),
deleteItem: removeMod,
bulkDeleteItems: (items: ContentItem[]) =>
Promise.all(items.map((item) => removeMod(item))).then(() => {}),
bulkDeleteItems: (items) => Promise.all(items.map((item) => removeMod(item))).then(() => {}),
refresh: () => initProjects('must_revalidate'),
browse: handleBrowseContent,
uploadFiles: handleUploadFiles,
@@ -809,7 +830,7 @@ provideContentManager({
showContentHint,
dismissContentHint,
shareItems: handleShareItems,
mapToTableItem: (item: ContentItem) => ({
mapToTableItem: (item) => ({
id: item.id,
project: item.project ?? {
id: item.file_name,
@@ -822,7 +843,7 @@ provideContentManager({
: undefined,
version: item.version ?? {
id: item.file_name,
version_number: formatMessage(commonMessages.unknownLabel),
version_number: formatMessage(messages.unknownVersion),
file_name: item.file_name,
},
versionLink:
@@ -839,7 +860,6 @@ provideContentManager({
}
: undefined,
enabled: item.enabled,
installing: item.installing,
}),
filterPersistKey: props.instance.path,
})

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