Compare commits

...
Author SHA1 Message Date
aecsocket fa769337cf Fix how analytics writes are serialized 2026-04-27 11:29:31 +01:00
453369ca07 feat(frontend): Make dashboard page localizable (#5727)
* Make dashboard page localizable

* dashboard sidebar

* prepr:frontend

* don't change the keys

* undo fix

* fix any err

* don't i18n csv

* prepr:frontend

* fix: do not use button key

* prepr:frontend

* capitalize string date

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-04-26 13:09:08 +00:00
Calum H.andGitHub faf593b2af fix: number parsing regression (#5906) 2026-04-24 18:39:30 +00:00
aecsocketandGitHub e3d6a498d0 Track new analytics metrics in backend (#5895)
* Allow filtering by project IDs in analytics route

* Download meta info in header

* add recursion limit

* Track playtime country

* fix clickhouse migrations
2026-04-24 15:43:25 +00:00
Michael H. 42cdcc7df9 fix: make sure staging uses staging 2026-04-24 16:25:59 +02:00
Truman GaoandGitHub e043a232bc fix: server search and medal server upgrade (#5903)
* fix: server search sort by

* fix: medal server listing upgrade button

* fix: bottom pagination spacing
2026-04-24 11:47:13 +00:00
Liam ElliottandGitHub c44ead2dbe fix: send boolean filter values without quotes (#5880)
Ensure boolean facet values like open_source=true are not wrapped in quotes in new_filters, matching MeiliSearch filter syntax.

Made-with: Cursor
2026-04-23 19:16:57 +00:00
aecsocketandGitHub 11ac27f71f Make mrpack downloads HTTPS-only (#5882)
* Add set of trusted download hosts for mrpacks

* split secure/insecure reqwest client

* make fetching https-only

* lint fix
2026-04-23 19:04:38 +00:00
SychicandGitHub 6862cf5ab2 Show orgs in project card when a project is owned by an org (#5892)
* fix: link to user using id instead of username

* feat: show org in project card

* fix: account for outdated documents

* refactor: use struct to store owner information

* fix: default new fields

* fix lint
2026-04-23 17:32:19 +00:00
Truman GaoandGitHub 16e1bf4611 fix: upgrade server flow to skip region (#5842)
* fix: upgrade server flow to skip region

* remove: previous hide region select implementation

* feat: implement skipping region select section for upgrade modal

* fix: modal not getting stripe customer and payment methods on page hard refresh

* refactor: pnpm prepr
2026-04-22 19:49:07 +00:00
SychicandGitHub 77e4c41480 fix(teams): accept username in edit member endpoint (#5852)
* fix(teams): accept username in edit member endpoint

* fix: remove unused import

* fix: use context to wrap error

* refactor: use context for error handling in edit_team_member

* fix: remove unused import

* fix: wrap database errors as internall errors

* fix: properly wrap errors
2026-04-21 13:47:49 +00:00
Truman GaoandGitHub cb93c641d6 fix: moderation checklist showing for unlisted status (#5875)
* fix: moderation checklist showing for unlisted status

* refactor: remove unused function
2026-04-20 21:01:31 +00:00
Calum H.andGitHub eebb353547 feat: changelog (#5874) 2026-04-20 22:51:31 +02:00
Truman GaoandGitHub 694ab09a01 fix: sort by not using relevance as default type (#5872) 2026-04-20 20:34:40 +00:00
Calum H.andGitHub da47c50320 fix: sticky header regression (#5869)
* fix: sticky header regression

* fix: github release changelog format for app release workflow

* fix: lint
2026-04-20 15:20:54 +00:00
Modrinth BotandGitHub bee4391df1 New translations from Crowdin (main) (#5867) 2026-04-20 11:11:42 +00:00
SychicandGitHub d1b122fb21 fix(maven): return escaped summary for project description (#5839)
* fix(maven): return escaped summary for project description

* build: add quickxml to labrinth

* fix(maven): use quickxml to escape xml special chars
2026-04-19 14:58:32 +00:00
François-Xavier TalbotandGitHub 281bf066de Fix typo lint (#5860) 2026-04-19 04:40:41 +00:00
coolbotandGitHub 68fde3ff97 Update all guidance URLs (#5858) 2026-04-19 02:52:51 +00:00
Prospector 7d6d938b99 changelog 2026-04-18 17:08:30 -07:00
Calum H.andGitHub 065759d1b8 fix: window on ssr error + cors problem with launcher meta (#5856) 2026-04-18 16:55:57 -07:00
Calum H.andGitHub 9b3fe6390e feat: fix bugs with layout + window controls (#5855) 2026-04-18 15:29:22 -07:00
Calum H.andGitHub 2236dd8ade fix: moderation locking fixes (#5843)
* fix: moderation locking fixes

* fix: lint

* wip: override always available

* fix: newmodal base z

* fix: cargo fmt
2026-04-18 18:55:33 +00:00
Calum H.andGitHub 3a44def301 chore: changelog (#5851)
* fix: modrinth hosting changelog in app changelog for github releases

* chore: changelog
2026-04-18 21:06:18 +02:00
Calum H.andGitHub 176d4301c3 feat: shared loading state + cleanup loading state management (#5835)
* feat: implement shared loading bar component and polished loading states across the app

* feat: align loading states + ensureQueryData changes

* fix: lint + bugs

* fix: skeleton for manage servers page

* fix: merge conflict fix
2026-04-18 18:46:39 +00:00
Calum H.andGitHub 3e32901737 feat: paper channel badges (#5850) 2026-04-18 18:13:08 +00:00
Calum H.andGitHub ab623dc325 feat: uncomment custom key event handler (#5831) 2026-04-18 17:50:20 +00:00
Truman GaoandGitHub bb6e24640c fix: project card having margin due to get actions fn (#5849) 2026-04-18 17:47:05 +00:00
Truman GaoandGitHub 15fc6d4e38 fix: upgrade to modrinth+ no longer fixed to bottom right (#5848) 2026-04-18 17:22:19 +00:00
Truman GaoandGitHub ed2f04322f feat: add edit version pages tabs (#5841)
* feat: add edit version pages tabs

* feat: switch to nav tabs instead of chips

* feat: show "Edit version" as modal title instead of specific page
2026-04-18 17:13:03 +00:00
b9e7b54b4e feat: improve recent worlds loading performance (#5079)
* Improve recent worlds loading performance

* Make recent worlds not cause a layout shift by loading them asynchronously

* Fix formatting

* fix formatting

---------

Co-authored-by: Creeperkatze <178587183+Creeperkatze@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: Calum H. <contact@cal.engineer>
Co-authored-by: Calum H. <calum@modrinth.com>
2026-04-18 17:01:54 +00:00
Calum H.andGitHub dd51c08a18 feat: clean up server power action buttons (#5836) 2026-04-17 17:11:52 +00:00
Calum H.andGitHub 5244060588 feat: refactor splash screen component (#5833) 2026-04-17 16:34:58 +00:00
aecsocketandGitHub 9483656881 Fix issues relating to app directory changes (#5826)
* Fix canceling app directory change

* Improve directory moving error messages

* tombi
2026-04-17 14:00:08 +00:00
Modrinth BotandGitHub 7c642f7078 New translations from Crowdin (main) (#5763) 2026-04-17 13:36:38 +00:00
3c2cc7568d feat: add collapsible library groups in app (#5739)
* feat: add collapsible library groups in app

* feat: use accordion rather than custom

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-04-16 13:44:52 +00:00
7b5c746757 fix(query): set default query retry to 1 (#5743)
* fix(query): set default query retry to 1

* fix(query): don't retry 404s and limit default retries to 3

* feat: expand status skipping checks

* feat: parallel fetch v2 and v3 in middleware

---------

Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-04-16 11:31:26 +00:00
5b5c8c06e3 feat: properly impl find for files (#5741)
* Initial file search impl

* Add replace functionality

* Rename to find, remove extra icon

* Put into seperate component

* Fix lint

* Change remaining search stuff to find

* Use ButtonStyled for buttons, use types from ace editor

* Make results label oriented left, add clear button to replace input

* Run fix

---------

Signed-off-by: Arthur <creeperkatze.dev@gmail.com>
Co-authored-by: Creeperkatze <178587183+Creeperkatze@users.noreply.github.com>
2026-04-16 10:59:38 +00:00
Calum H.andGitHub 4d68f3cea4 chore: changelog (#5823) 2026-04-15 22:09:54 +02:00
aecsocketandGitHub 546b117437 Tweak token prioritization in Typesense (#5776)
* Tweak toke prioritization in typesense

* tweaks

* allow configuring max_candidates

* tweak max_candidates

* final changes
2026-04-15 19:45:41 +00:00
Calum H.andGitHub 3d5f29a7a2 feat: continued post qa for servers in app (#5818)
* fix: intercom in app

* feat: Logs.vue dynamic console resizing with window + padding problem

* fix: search highlight with decorator + change to be better

* fix: qa

* fix: allow paper+purpur into app csp

* fix: lint
2026-04-15 19:16:05 +00:00
Truman GaoandGitHub 37b0f7ff98 fix: displayUrlEnding is undefined at times (#5811) 2026-04-15 18:50:05 +00:00
Truman GaoandGitHub 1603796856 feat: implement overlay scrollbar for app sidebar (#5820)
* feat: implement overlay scrollbar for app sidebar

* pnpm prepr
2026-04-15 16:53:08 +00:00
Truman GaoandGitHub 7c9a9f22d4 fix: project page header in mobile (#5817) 2026-04-15 16:14:19 +00:00
Truman GaoandGitHub d38d23dbf3 fix: post servers in app release QA (#5815)
* fix: billing page server plan heading

* fix: matching server page spacing with instance page

* feat: update server header buttons

* feat: add show ram as bytes always on

* fix: revert to large buttons

* feat: add hostname and server states in info card
2026-04-15 14:55:11 +00:00
aecsocketandGitHub f12bd7b4b8 Add utoipa info for v2 routes (#5775)
* wip: add v2 docs, routes to config, paths

* fix up path prefixes

* fix leading slashes

* fix slash route

* fix more slashes

* wip: full utopification of v2

* convert last few v2 routes to utoipa
2026-04-15 13:25:35 +00:00
Truman GaoandGitHub baee34b0b6 feat: add moderation checklist back to project page (#5814)
* fix: billing page server plan heading

* fix: matching server page spacing with instance page

* feat: update server header buttons

* feat: add show ram as bytes always on

* fix: revert to large buttons

* feat: add hostname and server states in info card

* feat: add publishing checklist to project page

* fix: markdown table style and max width

* fix: teleport overflow menu bad anchoring
2026-04-15 09:12:31 +00:00
Calum H.andGitHub 74bad7456c fix: blog wording (#5807)
* fix: blog wording

* fix: lint
2026-04-13 18:10:24 +00:00
a6f67581d7 chore: add hosting in app blog post (#5787)
* add blog post

* feat: update image

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-04-13 16:57:09 +00:00
Truman GaoandGitHub 02be7a8b82 fix: properties tab height with alert (#5802) 2026-04-13 14:56:53 +00:00
Calum H.andGitHub d5ad1cb823 fix: log sharing in app + clearing (#5801)
* fix: log wiping app

* fix: share modal rounded
2026-04-13 14:52:35 +00:00
Calum H.andGitHub b666747bc2 feat: guard null stats (#5800) 2026-04-13 14:23:31 +00:00
Calum H.andGitHub c1b0e4a692 fix: modal positioning (#5796) 2026-04-13 14:01:52 +00:00
Calum H.andGitHub 0713814d0c fix: prerelease fixes (#5793) 2026-04-12 23:58:50 +00:00
Calum H.andGitHub 0b8b4fb516 fix: modal stacking (#5792)
* fix: modal stacking

* fix: lint
2026-04-12 15:59:34 -07:00
Calum H.andGitHub d6e366b488 fix: workflow release (#5791) 2026-04-12 22:48:29 +00:00
Prospector 0682cc3c4f changelog 2026-04-12 15:13:51 -07:00
693a371d61 feat: server management in app (#5628)
* start new server settings tabs

* update properties tab to match design

* better stying in general tab

* feat: add suffix input for hostname field

* implement tables for allocations and DNS records

* add tags for dns record type

* small gap adjustment

* polish advanced page

* adjust properties page hierarchy

* fix searching properties, empty state and projection radius appearing

* pnpm prepr

* update copy to match designs

* fix suffix input component

* style fixes and match heading size

* small fix

* fix search allocations placeholder

* adjust table styles

* move all installation settings helper text to below input

* update icon to use overflow menu buttons

* fix modal to be consistent

* open advanced properties when search

* remove other and custom properties, and update styles

* remove hide/show all java versions

* handle mc 26

* refactor: move server settings pages into /ui and add app ServerSettingsModal

* hook up server pages for app

* add server page header to app

* hook up server settings modal

* use large size

* fix card box shadow style

* fix hostname input for app

* fix app/website card containers

* implement external tabs for billing and admin billing

* fix save banner fixed to parent instead of page body

* remove unused prop to FriendsList causing warning in app

* fix client-only not available for app

* fix bottom cut off

* wire node auth

* implement full copy buttons

* dedup copy button tailwind styles

* fix hover class not working in @apply

* fix spacing

* fix error validation styles

* apply consistent styles and spacing

* feat: update hosting server card (#5609)

* fix type errors

* fix some stylesheets not imported for storybook

* add server listing stories

* add fix for frontend stylesheet imports

* remove props.

* convert copy code to use tailwind

* update server listing component styles

* update server info label styles

* start status/player count info label, more style updates and fixes

* add new server card buttons

* hook up server cards and implement updated styles

* hook up on download button

* fix tauri throwing error when api returns 204 No Content

* hook up purchase server modal in app

* fix upgrading state loading icon

* pnpm prepr

* filter out servers past 30 days after cancellation

* do not apply opacity on lock or spiner icons

* fix disabled server icon background

* update pending change stage

* handle known suspension states

* refactor: reduce code duplication for server listing

* update disabled state text color

* fix loading icon color

* clean up copy

* fix disabled opacity for server card

* update server listing files kept to be countdown

* implement resubscribe modal

* implement proper provisioning state for resubscribe

* fix duplicate attribute and pnpm prepr

* feat: add shared UI package auth DI

* feat: update purchase server flow (#5714)

* implement server list empty state component

* fix stories and adjust spacing

* implement select plan design refresh

* implement auth for empty server list

* use refs instead of reactive

* pnpm prepr

* fix auth usage for empty servers list

* move app auth provider setup to src/providers/setup

* pnpm prepr

* fix max height

* style fix

* fix getCreds no auth is blocking api client

* implement servers guest plan modal and signin which redirects back to modal's next step

* refactor guest plan select logic into provider

* implement sign in or create account popup

* remove force empty serverList

* add download button for suspended mod and generic

* add handling for when user logs out

* QA pass style fixes

* more consistent page styles

* fix duplicate export

* refactor: remove all fallback stuff from resubscribe modal

* implement shared download latest backup util

* i18n pass

* pnpm prepr

* fix region being selected if ping failed

* pnpm prepr

* feat: servers in app finalization (#5744)

* feat: start on shared console implementation into logs and overview pages

* fix: terminal gap issues

* feat: swap word wrap for full screen

* fix: stats cards alignment

* fix: stats

* feat: fix console clear + remove copy

* fix: lint

* fix: use reset not clear

* feat: shared server header & overview page for app and website (#5736)

* feat: implement shared server header for app and website

* feat: implement wrapped overview page with shared composable and hook it up

* pnpm prepr

* fix: bugs

* qa: cleanup

* feat: root.vue shared layout

* feat: delete old options pages + fix discovery frontend

* fix: discovery

* fix: misc style/layout issues

* fix page padding

* fix: modal height jankiness

* feat: implement server install content in app and server setup modal with DI

* fix: spacing

* remove servers in app feature flag

* Revert "remove servers in app feature flag"

This reverts commit 86e284c4bd.

* fix: qa

* feat: remove legacy components from apps/frontend/src/components/ui/servers

---------

Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>

* qa pass (#5738)

* fix: qa

* feat: qa

* fix: server icon fetch fails due to global node auth race condition overriding each other

* fix: lint

* fix: server icon upload/sync and centralize logic

* fix: server settings modal not closing for server reset

* fix: better server sorting

* feat: copy address in server listing card

* fix: notification panel in modal and when overlapping with action bar

* fix: empty server list empty state flashing when refresh, fixed by adding isReady auth flag

* feat: use floating action bar for save banner

* fix: saving state in save bar

* fix: edit server icon styling

* fix: confirm modal to have consistent buttons

* feat: loading animation for server panel + caching improvements for app

* pnpm prepr

* feat: search page deduplication (#5754)

* fix: action bar behind modal

* fix: remove warning modal for stopping

* fix: server cards states

* we hate webkit we hate webkit

* fix: update allocation creation to not use modal

* fix: properties tab spacing and styles

* feat: add files tab copy

* fix: advanced properties icon

* fix: remove back to all servers link

* feat: add files tab link in copy

* fix: server header styles to be consistent with instance

* fix: add header icons back

* feat: update instance settings icon to be consistent

* fix: icon container

* feat: upload state persistence across tabs

* fix: server labels text wrapping

* fix: use surface-5 border

* fix: loading spinner showing with onboarding below

* feat: new server button shows purchase modal in website

* fix: billing page not showing quarterly interval

* fix: server downgrade not showing updated subscription notification

* fix: server settings invalidate saved state and remove server context provider since its already provided in the page

* pnpm prepr

* add stripe publishable key to app build

* feat: console highlighting

* fix: rename servers title to modrinth hosting

* feat: search fix

* fix: qa/styles

* fix: ip click active and remove power dont ask again

* fix: qa

* feat: highlighting fix console

* fix: disable conflicts action

* fix: error dismiss bug

* feat: modal clarification

* fix: files perms issue

* fix: lint

* feat: modal fix

* enable show uptime

* fix: add loading state to edit server icon

* fix: notification panel take in has sidebar from settings

* fix: consistency pass on app settings

* fix: consistency pass on instance settings

* pnpm prepr

* fix: nagivate to billing button in app to go to website

* fix: stripe return url in app causing app to open modrinth.com in tauri

* refactor: better show polling UI code

* fix: new server polling comparison to use server ids instead of length

* fix: buttonstyled story

* fix: button styling

* fix: content.vue regression

* feat: project url redirects

* fix: breadcrumbs

* fix: purchase with newly added card

* fix: console ordering problems

* fix: app-frontend missing env config and staging environment

* fix: log syncing for instances and server panel accidentally

* fix: QA issues

* fix: server page loading state

* fix: stats card logic

* fix: lint

* fix: qa

* fix: console height padding

* fix: terminal padding + loading indicator

* feat: update medal server listing styling

* fix: no upgrade button for medal server listing in app

* fix: go to overview instead of content tab after onboarding

* fix: qa

* fix: teleport modals to body

* fix: logs tab + qa

* fix: local storage for user preferences

* fix: qa loading indic

* feat: considitonal debug and trace

* fix: jump to top on install bug

* feat: swap out server hard drive icon to server stack icon

* feat: servers in app feature flag default true

* fix: highlight row ufll

* fix: webkit thing onto a tag

* fix: input field

* fix: clear fix

* fix: lint

* fix: fmt

* feat: improve share modal and bring it back for sharing log

* pnpm prepr

* fix: menu overflowing

* feat: remove servers in app feature flag

* fix: server stat charts no longer showing color

* fix: library nav no primary state

* fix: better modal height and width

* fix: highlighting bugs

* fix: empty states

* fix: delay import to fix overview page slow load on MacOS

* fix: medal server listing too bright on light mode

* fix: admon analysis + fix logs

* fix: bug

* fix: clear purchase intent from sign-in after closing modal

* performance: improve server manage stats loading by splitting reactivity

* fix: deploy + admon + disable highlighting

* fix: clippy

---------

Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>

* feat: temp wrangler

* fix: lint

* fix: logs upload

* fix: console empty state and admon regressions

* fix: fields

* feat: log deleting + prefetch for Logs.vue

* feat: move delete before share

* feat: clear endpoint

* feat: we ball!

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-04-12 21:38:08 +00:00
shellawaandGitHub a2a97d1313 fix: use nullish coalescing instead of OR on fraction digits (#5788) 2026-04-12 16:14:44 +00:00
aecsocketandGitHub 71e4f7cb91 Update ad webview logic (#5774)
* add clarifying comments to ad webview code

* properly manage adview show/hiding

* check if webviews manage visibility on Windows

* comment out testing code

* add clarifying comments
2026-04-11 15:49:52 +00:00
Truman GaoandGitHub 1a51e58297 fix: orgs member settings not persisting by shallow-clones each member object (#5772) 2026-04-08 17:57:58 +00:00
aecsocketandGitHub a9c417d1d1 Switch from Swagger to Scalar for OpenAPI docs (#5766)
* Switch from Swagger to Scalar for OpenAPI docs

* remove old comments
2026-04-07 22:14:26 +00:00
aecsocketandGitHub de4f0bffb0 Add server ID to Intercom JWT payload (#5769) 2026-04-07 22:14:15 +00:00
aecsocketandGitHub e71a8c10fa Fix passing intercom identity from CF v2 (#5753) 2026-04-04 14:50:32 -07:00
ProspectorandGitHub c4b3c6e8d6 add sign in redirect path (#5746) 2026-04-04 20:33:09 +00:00
aecsocketandGitHub 54c45ac9f3 Fix passing intercom identity from CF (#5752) 2026-04-04 10:40:26 -07:00
aecsocketandGitHub e5f600ddd7 Fix buffer allocations in server pinging code (#5751) 2026-04-04 17:22:40 +00:00
4a7525d0a1 Fix mobile navigation media query (#5749)
Co-authored-by: Creeperkatze <178587183+Creeperkatze@users.noreply.github.com>
2026-04-04 15:50:04 +00:00
Prospector d0d3aaf09b changelog 2026-04-04 08:48:57 -07:00
aecsocketandGitHub bb3506823d Add secure support connection to website (#5750)
* wip: icom jwts

* should fix auth token passing

* add to wrangler
2026-04-04 08:47:03 -07:00
ProspectorandGitHub 3091021194 fix: no gap after navtabs on collections (#5740)
* fix: no gap after navtabs on collections

* fix collection sorting
2026-04-04 14:53:53 +00:00
Prospector dc96043adc app changelog 2026-04-03 16:38:43 -07:00
aecsocketandGitHub 92bf2e5c29 Install transitive dependencies of versions properly (#5745)
* install recursive deps properly

* fix up
2026-04-03 20:29:08 +00:00
Prospector a6d359e9c1 Revert "beenz"
This reverts commit 35033ccc03.
2026-04-02 13:18:20 -07:00
Calum H.andGitHub ddd1a36506 feat: allow changelog comment from 3rd party PRs (#5724) 2026-04-02 19:41:53 +00:00
Prospector 933fdba388 Revert "give beenz"
This reverts commit c52abece44.
2026-04-02 12:40:39 -07:00
Calum H.andGitHub fa4711ff7b feat: implement improved flow for server panel edit installation (#5711)
* feat: implement improved flow for server panel edit installation

* feat: installation form finalized

* feat: error state for InstallingBanner

* feat: action button refactor + save banner text fix

* fix: lint

* fix: content card alignment

* feat: better copy

* fix: lint

* fix: hide shift click + fix NeoForge chip

* fix: lint
2026-04-01 17:53:19 +00:00
Prospector c52abece44 give beenz 2026-04-01 03:03:26 -07:00
Prospector 35033ccc03 beenz 2026-04-01 01:42:18 -07:00
Prospector 97734a98f9 fix error 2026-04-01 01:42:01 -07:00
Calum H.andGitHub 3978e3dead fix: try fix virtual scrolling regression (#5723) 2026-03-31 20:40:04 +00:00
Calum H.andGitHub 5939d2a4e7 fix: mobile tooltip bug for files tab (#5722) 2026-03-31 20:33:02 +00:00
Truman GaoandGitHub 4224ef45b3 feat: add shared UI package auth DI (#5720)
* feat: add shared UI package auth DI

* use refs instead of reactive

* pnpm prepr

* move app auth provider setup to src/providers/setup
2026-03-31 17:15:35 +00:00
xinyihlandGitHub e2bfed177d fix: billing charges error 500 (#5718) 2026-03-31 07:06:26 +00:00
Truman GaoandGitHub 7f92706e5f improve app error modal (#5710) 2026-03-31 07:00:59 +00:00
François-Xavier TalbotandGitHub 90deb7310e Fix daedalus NeoForge (#5713)
* Support NeoForge version for year-based Minecraft versions

* Run on CI

* Fix

* Fix branch

* Undo branch switch
2026-03-30 17:53:29 +00:00
Truman GaoandGitHub 4cd6c1a72d fix modpacks no loader (#5707) 2026-03-30 09:21:20 +00:00
NitrrineandGitHub 3a8561cf35 fix: save banner is getting hidden by some parts of the ui (#5697) 2026-03-30 09:20:55 +00:00
Modrinth BotandGitHub a3f80dcb66 New translations from Crowdin (main) (#5708) 2026-03-30 09:20:30 +00:00
Calum H.andGitHub 0ee58867e8 feat: warning filter + remove client only filter as it's useless (#5690)
* feat: warning filter

* fix: remove client_only filter
2026-03-27 22:00:02 +00:00
Prospector d4c2fdb9ef Revert "revert release workflow changes"
This reverts commit fc87506745.
2026-03-27 15:01:52 -07:00
Calum H.andGitHub e6b061f38c fix: paper/purpur vers mismatch (#5687)
* fix: paper/purpur

* fix: use fill api

* fix: lint
2026-03-27 17:24:16 +00:00
Calum H.andGitHub 87122cf9bd feat: console component (#5685) 2026-03-27 15:44:08 +00:00
Prospector fc87506745 revert release workflow changes 2026-03-26 18:30:02 -07:00
Prospector 6ba41ba17f clean up changelog, remove unnecessary technical details 2026-03-26 18:21:12 -07:00
Prospector 628634772e changelog 2026-03-26 18:04:59 -07:00
François-Xavier TalbotandGitHub b68aeddedc hosting: Copy ID button for backups when developer mode is on (#5681)
* Copy ID button in backups tab

* Remove codex slop
2026-03-27 00:18:33 +00:00
Calum H.andGitHub c5a0c71424 feat: vite 8 app frontend (#5680) 2026-03-27 00:17:51 +00:00
Calum H.andGitHub 4394092928 feat: better tooltips for mods in content tab hosting panel (#5679)
* feat: better tooltips for mods in content tab hosting panel

* feat: qa
2026-03-26 22:55:08 +00:00
aecsocketandGitHub ef1ffa6577 Fix payout sync response schema (#5644) 2026-03-26 22:44:54 +00:00
Calum H.andGitHub b11b54cbc9 fix: various content fixes (#5676)
* fix: wrong lock field

* fix: install_stage locking up due to previous failure stored as stale snapshot

* fix: Error when updating instance
Fixes #5671

* fix: prepr
2026-03-26 22:30:17 +00:00
ProspectorandGitHub 36f62a3285 refactor: move flags into settings, change icon (#5678)
* refactor: move flags into settings, change icon

* fix: use ButtonStyled for app
2026-03-26 21:10:01 +00:00
Calum H.andGitHub 381ea51cce refactor: align files tab with content tab design (#5621)
* fix: files.vue bugs before styling changes

* feat: move files tab to shared layout structure

* fix: qa

* fix: qa

* fix: bugs

* fix: lint

* fix: admonition cleanup with progress + actions

* fix: cleanup

* fix: modals

* fix: admon title

* fix: i18n standard

* fix: lint + i18n pass

* fix: remove transition

* fix: type errors

* feat: files tab in app

* fix: qa

* fix: backup item minmax

* fix: use ContentPageHeader for server panel

* fix: lint

* fix: lint

* fix: lint

* feat: page leave safety

* fix: lint

* fix: cargo fmt fix

* fix: blank in prod

* fix: content card table stuff

* Revert "fix: blank in prod"

This reverts commit 74758fe185.

* fix: import

* feat: browse worlds/servers flow

* fix: worlds tab parity with content tab

* fix: perf bug + shader filter pill copy

* feat: singleplayer filter

* fix: ordering

* fix: breadcrumbs

* fix: lint

* fix: qa

* feat: store server proj id when adding to a non-linked instance

* fix: lint

* fix: i18n + qa

* fix: conflict

* qa: already installed modal + placeholders not server-specific

* fix: qa

* fix: add + edit server modals

* fix: qa

* fix: security

* fix: devin flags

* fix: lint

* chore: change file to break build cache

* fix: admon

* fix: import path stuff

* feat: qa

* fix: fmt fmt idiot

---------

Signed-off-by: Calum H. <calum@modrinth.com>
2026-03-26 18:55:15 +00:00
Truman GaoandGitHub 706eb800cb fix: website visual issues (#5675)
* fix no modpack loader showing as resource pack loader

* fix table overflow, add game version tags "+ {num}" overflow menu

* pnpm prepr
2026-03-26 18:40:44 +00:00
Calum H.andGitHub f1648298c4 fix: neoforge not existing for 26.1 breaking vers picker (#5674)
* fix: neoforge for 26.1 -> other vers being picked not existing causing version picker to break

* fix: lint
2026-03-26 17:53:27 +00:00
Prospector 3c3cde1908 changelog 2026-03-26 00:22:40 -07:00
ProspectorandGitHub 274325d97c fix: settings page error (#5668) 2026-03-26 00:21:19 -07:00
aecsocketandGitHub da48a12551 Only mark servers as offline if they fail pings 3+ times (#5664)
* wip: online status fix

* use INCR

* properly clear cache
2026-03-26 06:34:20 +00:00
François-Xavier TalbotandGitHub bf24ed8d12 Add feature flag to force Archon requests to be traced (#5666) 2026-03-26 06:34:04 +00:00
aecsocketandGitHub 0731654a1c maybe fix daedalus (#5665) 2026-03-26 06:33:58 +00:00
768 changed files with 67569 additions and 23774 deletions
@@ -22,4 +22,5 @@ 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
@@ -0,0 +1,36 @@
---
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:
pull_request_target:
types: [opened, reopened]
workflow_dispatch:
inputs:
+3
View File
@@ -27,12 +27,15 @@ 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:
+27 -2
View File
@@ -9,6 +9,7 @@ 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
@@ -23,6 +24,25 @@ 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:
@@ -32,12 +52,17 @@ jobs:
branch: ${{ env.VERSION_TAG }}
use_unzip: true
- name: 📝 Extract app changelog
env:
VERSION: ${{ env.VERSION_TAG }}
run: npx --yes tsx scripts/build-theseus-release-notes.ts
- name: 🛠️ Generate version manifest
run: |
# Reference: https://tauri.app/plugin/updater/#server-support
jq -nc \
--arg versionTag "${VERSION_TAG#v}" \
--arg releaseNotes "See the full changelog at https://modrinth.com/news/changelog" \
--rawfile releaseNotes release-notes.md \
--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" \
@@ -120,7 +145,7 @@ jobs:
gh release create "$VERSION_TAG" \
--title "Modrinth App ${VERSION}" \
--notes "See the full changelog at https://modrinth.com/news/changelog" \
--notes-file release-notes.md \
"${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" \
+6
View File
@@ -78,3 +78,9 @@ storybook-static
# frontend robots.txt
apps/frontend/src/public/robots.txt
# Oh My Code
.omc/
# Local dry-run output for scripts/build-theseus-release-notes.mjs
/test_result.md
+3 -1
View File
@@ -29,5 +29,7 @@
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
},
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore"
}
-1
View File
@@ -1 +0,0 @@
See [CLAUDE.md](./CLAUDE.md) for all project instructions and guidelines.
+1
View File
@@ -86,6 +86,7 @@ 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
+33 -79
View File
@@ -27,7 +27,7 @@ checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d"
dependencies = [
"actix-utils",
"actix-web",
"derive_more 2.0.1",
"derive_more 2.1.1",
"futures-util",
"log",
"once_cell",
@@ -46,7 +46,7 @@ dependencies = [
"actix-web",
"bitflags 2.9.4",
"bytes",
"derive_more 2.0.1",
"derive_more 2.1.1",
"futures-core",
"http-range",
"log",
@@ -72,7 +72,7 @@ dependencies = [
"brotli",
"bytes",
"bytestring",
"derive_more 2.0.1",
"derive_more 2.1.1",
"encoding_rs",
"flate2",
"foldhash",
@@ -226,7 +226,7 @@ dependencies = [
"bytestring",
"cfg-if",
"cookie 0.16.2",
"derive_more 2.0.1",
"derive_more 2.1.1",
"encoding_rs",
"foldhash",
"futures-core",
@@ -1902,6 +1902,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cookie"
version = "0.16.2"
@@ -2450,21 +2459,23 @@ dependencies = [
[[package]]
name = "derive_more"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case 0.10.0",
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.106",
"unicode-xid",
]
@@ -4914,7 +4925,7 @@ dependencies = [
"const_format",
"dashmap",
"deadpool-redis",
"derive_more 2.0.1",
"derive_more 2.1.1",
"dotenv-build",
"dotenvy",
"either",
@@ -4941,6 +4952,7 @@ dependencies = [
"paste",
"path-util",
"prometheus",
"quick-xml 0.38.3",
"rand 0.8.5",
"rand_chacha 0.3.1",
"redis",
@@ -4973,7 +4985,7 @@ dependencies = [
"urlencoding",
"utoipa",
"utoipa-actix-web",
"utoipa-swagger-ui",
"utoipa-scalar",
"uuid 1.18.1",
"validator",
"webp",
@@ -5515,7 +5527,7 @@ name = "modrinth-util"
version = "0.0.0"
dependencies = [
"actix-web",
"derive_more 2.0.1",
"derive_more 2.1.1",
"dotenvy",
"eyre",
"modrinth-log",
@@ -5581,7 +5593,7 @@ dependencies = [
"arc-swap",
"bytes",
"chrono",
"derive_more 2.0.1",
"derive_more 2.1.1",
"reqwest 0.12.24",
"rust_decimal",
"rust_iso3166",
@@ -6592,7 +6604,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
name = "path-util"
version = "0.0.0"
dependencies = [
"derive_more 2.0.1",
"derive_more 2.1.1",
"itertools 0.14.0",
"serde",
"typed-path",
@@ -7950,40 +7962,6 @@ 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"
@@ -9308,7 +9286,7 @@ dependencies = [
name = "sqlx-tracing"
version = "0.2.0"
dependencies = [
"derive_more 2.0.1",
"derive_more 2.1.1",
"futures",
"opentelemetry",
"opentelemetry-testing",
@@ -10181,7 +10159,7 @@ dependencies = [
"daedalus",
"dashmap",
"data-url",
"derive_more 2.0.1",
"derive_more 2.1.1",
"dirs",
"discord-rich-presence",
"dotenvy",
@@ -10189,6 +10167,7 @@ dependencies = [
"either",
"encoding_rs",
"enumset",
"eyre",
"flate2",
"fs4",
"futures",
@@ -10241,6 +10220,7 @@ dependencies = [
name = "theseus_gui"
version = "1.0.0-local"
dependencies = [
"async_zip",
"chrono",
"daedalus",
"dashmap",
@@ -10258,6 +10238,7 @@ dependencies = [
"tauri-build",
"tauri-plugin-deep-link",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-http",
"tauri-plugin-opener",
"tauri-plugin-os",
@@ -11197,30 +11178,17 @@ dependencies = [
]
[[package]]
name = "utoipa-swagger-ui"
version = "9.0.2"
name = "utoipa-scalar"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
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"
@@ -12605,20 +12573,6 @@ 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"
+3 -2
View File
@@ -64,7 +64,7 @@ darling = { version = "0.23" }
dashmap = "6.1.0"
data-url = "0.3.2"
deadpool-redis = { git = "https://github.com/modrinth/deadpool", rev = "db5fb00b036ecc8fe5f18853c559b745ffe47bde", version = "0.22.1" }
derive_more = "2.0.1"
derive_more = "2.1.1"
directories = "6.0.0"
dirs = "6.0.0"
discord-rich-presence = "1.0.0"
@@ -179,6 +179,7 @@ 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"
@@ -207,7 +208,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-swagger-ui = { version = "9.0.2", features = ["actix-web", "vendored"] }
utoipa-scalar = { version = "0.3.0", default-features = false }
uuid = "1.18.1"
validator = "0.20.0"
webp = { version = "0.3.1", default-features = false }
+3 -1
View File
@@ -22,6 +22,7 @@
"@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",
@@ -34,6 +35,7 @@
"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",
@@ -58,7 +60,7 @@
"sass": "^1.74.1",
"tailwindcss": "^3.4.4",
"typescript": "^5.5.4",
"vite": "^6.0.0",
"vite": "^8.0.0",
"vue-component-type-helpers": "^3.1.8",
"vue-tsc": "^2.1.6"
},
+186 -123
View File
@@ -1,5 +1,12 @@
<script setup>
import { AuthFeature, PanelVersionFeature, TauriModrinthClient } from '@modrinth/api-client'
import {
AuthFeature,
NodeAuthFeature,
nodeAuthState,
PanelVersionFeature,
TauriModrinthClient,
VerboseLoggingFeature,
} from '@modrinth/api-client'
import {
ArrowBigUpDashIcon,
ChangeSkinIcon,
@@ -11,15 +18,12 @@ import {
LibraryIcon,
LogInIcon,
LogOutIcon,
MaximizeIcon,
MinimizeIcon,
NewspaperIcon,
NotepadTextIcon,
PlusIcon,
RefreshCwIcon,
RestoreIcon,
RightArrowIcon,
ServerIcon,
ServerStackIcon,
SettingsIcon,
UserIcon,
WorldIcon,
@@ -28,13 +32,13 @@ import {
import {
Admonition,
Avatar,
Button,
ButtonStyled,
commonMessages,
ContentInstallModal,
CreationFlowModal,
defineMessages,
I18nDebugPanel,
LoadingBar,
NewsArticleCard,
NotificationPanel,
OverflowMenu,
@@ -49,10 +53,11 @@ import {
useVIntl,
} from '@modrinth/ui'
import { formatBytes, renderString } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import { useQuery, useQueryClient } 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'
@@ -61,7 +66,6 @@ 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'
@@ -79,7 +83,9 @@ 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'
@@ -106,8 +112,10 @@ 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 { useLoading, useTheming } from '@/store/state'
import { useTheming } from '@/store/state'
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
import { get_available_capes, get_available_skins } from './helpers/skins'
@@ -126,17 +134,34 @@ 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),
@@ -394,17 +419,112 @@ 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()
const loading = useLoading()
loading.setEnabled(false)
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 error = useError()
const errorModal = ref()
@@ -454,9 +574,31 @@ 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
@@ -930,6 +1072,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</script>
<template>
<WindowControls />
<SplashScreen v-if="!stateFailed" ref="splashScreen" data-tauri-drag-region />
<div id="teleports"></div>
<div
@@ -977,13 +1120,6 @@ 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"
@@ -998,6 +1134,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<NavButton
v-tooltip.right="'Library'"
to="/library"
:is-primary="(r) => r.path === '/library' || r.path === '/library'"
:is-subpage="
() =>
route.path.startsWith('/instance') ||
@@ -1007,6 +1144,14 @@ 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 />
@@ -1125,22 +1270,6 @@ 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>
@@ -1176,14 +1305,14 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</div>
</transition>
<div
class="loading-indicator-container h-8 fixed z-50"
class="loading-indicator-container h-8 fixed z-50 pointer-events-none"
:style="{
top: 'calc(var(--top-bar-height))',
left: 'calc(var(--left-bar-width))',
width: 'calc(100% - var(--left-bar-width) - var(--right-bar-width))',
}"
>
<ModrinthLoadingIndicator />
<LoadingBar position="absolute" />
</div>
<div
v-if="themeStore.featureFlags.page_path"
@@ -1219,40 +1348,36 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</Admonition>
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
<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 overflow-auto"
class="app-sidebar mt-px shrink-0 flex flex-col border-0 border-l-[1px] border-[--brand-gradient-border] border-solid"
:class="{ 'has-plus': hasPlus }"
>
<div
class="app-sidebar-scrollable flex-grow shrink overflow-y-auto relative"
v-overlay-scrollbars="sidebarOverlayScrollbarsOptions"
class="app-sidebar-scrollable flex-grow shrink 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 pr-1 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"
>
<div class="p-4 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="py-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<suspense>
<FriendsList
:credentials="credentials"
:sign-in="() => signIn()"
:refresh-credentials="fetchCredentials"
/>
<FriendsList :credentials="credentials" :sign-in="() => signIn()" />
</suspense>
</div>
<div v-if="news && news.length > 0" class="p-4 pr-1 flex flex-col items-center">
<div v-if="news && news.length > 0" class="p-4 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
@@ -1282,8 +1407,8 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</div>
</div>
<I18nDebugPanel />
<NotificationPanel has-sidebar />
<PopupNotificationPanel has-sidebar />
<NotificationPanel :has-sidebar="sidebarVisible" />
<PopupNotificationPanel :has-sidebar="sidebarVisible" />
<ErrorModal ref="errorModal" />
<MinecraftAuthErrorModal ref="minecraftAuthErrorModal" />
<ContentInstallModal
@@ -1319,72 +1444,6 @@ 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;
@@ -1409,6 +1468,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
.app-grid-statusbar {
grid-area: status;
padding-right: var(--window-controls-width, 0px);
}
[data-tauri-drag-region-exclude] {
@@ -1600,6 +1660,13 @@ 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;
@@ -1611,10 +1678,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
height: 2.5rem !important;
}
.window-controls {
display: flex !important;
}
.info-card {
right: 22rem;
}
@@ -45,6 +45,14 @@
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 {
@@ -77,12 +85,10 @@ body {
}
a {
color: var(--color-link);
color: inherit;
text-decoration: none;
&:hover {
text-decoration: none;
}
-webkit-font-smoothing: antialiased;
will-change: filter;
}
.badge {
@@ -174,4 +180,63 @@ 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,6 +10,7 @@ import {
TrashIcon,
} from '@modrinth/assets'
import {
Accordion,
DropdownSelect,
formatLoader,
injectNotificationManager,
@@ -133,12 +134,33 @@ 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
@@ -280,18 +302,21 @@ const filteredResults = computed(() => {
<span class="font-semibold text-secondary">{{ selected }}</span>
</DropdownSelect>
</div>
<div
<Accordion
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)"
>
<div v-if="instanceSection.key !== 'None'" class="divider">
<p>{{ instanceSection.key }}</p>
<hr aria-hidden="true" />
</div>
<template v-if="instanceSection.key !== 'None'" #title>
<span class="text-base">{{ instanceSection.key }}</span>
</template>
<section class="instances">
<Instance
v-for="instance in instanceSection.value"
@@ -301,7 +326,7 @@ const filteredResults = computed(() => {
@contextmenu.prevent.stop="(event) => handleRightClick(event, instance.path)"
/>
</section>
</div>
</Accordion>
<ConfirmDeleteInstanceModal ref="confirmModal" @delete="deleteProfile" />
<ContextMenu ref="instanceOptions" @option-clicked="handleOptionsClick">
<template #play> <PlayIcon /> Play </template>
@@ -316,73 +341,7 @@ 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 {
@@ -1,142 +0,0 @@
<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,6 +3,7 @@
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"
@@ -128,6 +129,16 @@ 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,6 +6,7 @@ import {
HammerIcon,
LogInIcon,
UpdatedIcon,
WrenchIcon,
XIcon,
} from '@modrinth/assets'
import { ButtonStyled, Collapsible, injectNotificationManager } from '@modrinth/ui'
@@ -155,7 +156,7 @@ async function copyToClipboard(text) {
<template>
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
<div class="modal-body">
<div class="modal-body max-w-[550px]">
<div class="markdown-body">
<template v-if="errorType === 'minecraft_auth'">
<template v-if="metadata.network">
@@ -280,7 +281,7 @@ async function copyToClipboard(text) {
{{ debugInfo }}
</template>
<template v-if="hasDebugInfo">
<hr />
<div class="w-full h-[1px] bg-surface-5 mb-3"></div>
<p>
If nothing is working and you need help, visit
<a :href="supportLink">our support page</a>
@@ -296,28 +297,47 @@ 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="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 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>
</template>
</div>
@@ -2,6 +2,7 @@
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
@@ -12,18 +13,23 @@ type Instance = {
name: string
}
defineProps<{
instance: Instance
}>()
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
})
</script>
<template>
<div class="flex justify-between items-center border-0 border-b border-solid border-divider pb-4">
<router-link
:to="`/instance/${encodeURIComponent(instance.path)}`"
tabindex="-1"
class="flex flex-col gap-4 text-primary"
>
<router-link :to="instanceLink" 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"
@@ -43,9 +49,7 @@ defineProps<{
</span>
</router-link>
<ButtonStyled>
<router-link :to="`/instance/${encodeURIComponent(instance.path)}`">
<LeftArrowIcon /> Back to instance
</router-link>
<router-link :to="instanceLink"> <LeftArrowIcon /> Back to instance </router-link>
</ButtonStyled>
</div>
</template>
@@ -3,6 +3,7 @@
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),
@@ -1,185 +0,0 @@
<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>
@@ -1,142 +0,0 @@
<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,11 +1,9 @@
<script setup>
import { Button, injectNotificationManager } from '@modrinth/ui'
import { Button, injectNotificationManager, ProjectCard } from '@modrinth/ui'
import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get_project, get_version } from '@/helpers/cache.js'
import { get_categories } from '@/helpers/tags.js'
import { get_project_v3, get_version } from '@/helpers/cache.js'
import { injectContentInstall } from '@/providers/content-install'
const { handleError } = injectNotificationManager()
@@ -14,26 +12,22 @@ 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(version.value.project_id, 'must_revalidate').catch(
project.value = await get_project_v3(version.value.project_id, 'must_revalidate').catch(
handleError,
)
} else {
project.value = await get_project(event.id, 'must_revalidate').catch(handleError)
project.value = await get_project_v3(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()
},
})
@@ -52,13 +46,22 @@ async function install() {
</script>
<template>
<ModalWrapper ref="confirmModal" :header="`Install ${project?.title}`">
<ModalWrapper ref="confirmModal" :header="`Install ${project?.name}`">
<div class="modal-body">
<SearchCard
:project="project"
<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"
class="project-card"
:categories="categories"
@open="confirmModal.hide()"
/>
<div class="button-row">
<div class="markdown-body">
@@ -0,0 +1,98 @@
<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-2 mr-1">
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 -ml-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="ml-2 w-full text-base text-primary font-medium m-0">
<h3 v-else class="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="ml-4 mr-1 text-base text-primary font-medium m-0">
<h3 v-if="loading" class="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 ml-4 mr-1">
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse">
<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 ml-4 mr-1">
<div class="text-sm">
<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="
'pl-4 pr-3 flex w-full items-center bg-transparent border-0 p-0' +
'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 ml-4 mr-1"
class="group grid items-center grid-cols-[auto_1fr_auto] gap-2 hover:bg-button-bg transition-colors rounded-full 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.name)
const icon: Ref<string | undefined> = ref(instance.icon_path)
const groups = ref(instance.groups)
const title = ref(instance.value.name)
const icon: Ref<string | undefined> = ref(instance.value.icon_path)
const groups = ref([...instance.value.groups])
const newCategoryInput = ref('')
const installing = computed(() => instance.install_stage !== 'installed')
const installing = computed(() => instance.value.install_stage !== 'installed')
async function duplicateProfile() {
await duplicate(instance.path).catch(handleError)
await duplicate(instance.value.path).catch(handleError)
trackEvent('InstanceDuplicate', {
loader: instance.loader,
game_version: instance.game_version,
loader: instance.value.loader,
game_version: instance.value.game_version,
})
}
@@ -53,7 +53,7 @@ const availableGroups = computed(() => [
async function resetIcon() {
icon.value = undefined
await edit_icon(instance.path, null).catch(handleError)
await edit_icon(instance.value.path, null).catch(handleError)
trackEvent('InstanceRemoveIcon')
}
@@ -71,7 +71,7 @@ async function setIcon() {
if (!value) return
icon.value = value
await edit_icon(instance.path, icon.value).catch(handleError)
await edit_icon(instance.value.path, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')
}
@@ -102,7 +102,7 @@ watch(
[title, groups, groups],
async () => {
if (removing.value) return
await edit(instance.path, editProfileObject.value).catch(handleError)
await edit(instance.value.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.path
const path = instance.value.path
trackEvent('InstanceRemove', {
loader: instance.loader,
game_version: instance.game_version,
loader: instance.value.loader,
game_version: instance.value.game_version,
})
await router.push({ path: '/' })
@@ -198,45 +198,48 @@ const messages = defineMessages({
<template>
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="removeProfile" />
<div class="block">
<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"
<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,
},
]"
>
<EditIcon aria-hidden="true" class="h-4 w-4 text-primary" />
</div>
<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>
</div>
<template #select>
<UploadIcon />
{{ icon ? formatMessage(messages.replaceIcon) : formatMessage(messages.selectIcon) }}
</template>
<template #remove> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
</OverflowMenu>
</div>
</div>
<label for="instance-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
<label for="instance-name" class="m-0 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.name) }}
</label>
<div class="flex">
@@ -249,76 +252,82 @@ const messages = defineMessages({
/>
</div>
<template v-if="instance.install_stage == 'installed'">
<div>
<h2
id="duplicate-instance-label"
class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block"
>
<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">
{{ formatMessage(messages.duplicateInstance) }}
</h2>
<p class="m-0 mb-2">
<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">
{{ formatMessage(messages.duplicateInstanceDescription) }}
</p>
</div>
<ButtonStyled>
</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">
<button
v-tooltip="installing ? formatMessage(messages.duplicateButtonTooltipInstalling) : null"
aria-labelledby="duplicate-instance-label"
:disabled="installing"
@click="duplicateProfile"
aria-labelledby="delete-instance-label"
:disabled="removing"
class="w-fit !shadow-none"
@click="deleteConfirmModal.show()"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
<SpinnerIcon v-if="removing" class="animate-spin" />
<TrashIcon v-else />
{{
removing
? formatMessage(messages.deletingInstanceButton)
: formatMessage(messages.deleteInstanceButton)
}}
</button>
</ButtonStyled>
</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>
<p class="m-0">
{{ formatMessage(messages.deleteInstanceDescription) }}
</p>
</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,9 +22,11 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideHooks = ref(
!!instance.hooks.pre_launch || !!instance.hooks.wrapper || !!instance.hooks.post_exit,
!!instance.value.hooks.pre_launch ||
!!instance.value.hooks.wrapper ||
!!instance.value.hooks.post_exit,
)
const hooks = ref(instance.hooks ?? globalSettings.hooks)
const hooks = ref(instance.value.hooks ?? globalSettings.hooks)
const editProfileObject = computed(() => {
const editProfile: {
@@ -40,7 +42,7 @@ const editProfileObject = computed(() => {
watch(
[overrideHooks, hooks],
async () => {
await edit(instance.path, editProfileObject.value)
await edit(instance.value.path, editProfileObject.value)
},
{ deep: true },
)
@@ -99,57 +101,57 @@ const messages = defineMessages({
<template>
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
<h2 class="m-0 m-0 text-lg font-semibold 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-2 mb-1 text-lg font-extrabold text-contrast">
<h2 class="mt-6 m-0 text-lg font-semibold 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 mt-2"
wrapper-class="w-full my-2.5"
/>
<p class="m-0">
{{ formatMessage(messages.preLaunchDescription) }}
</p>
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast">
<h2 class="mt-6 m-0 text-lg font-semibold 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 mt-2"
wrapper-class="w-full my-2.5"
/>
<p class="m-0">
{{ formatMessage(messages.wrapperDescription) }}
</p>
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast">
<h2 class="mt-6 m-0 text-lg font-semibold 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 mt-2"
wrapper-class="w-full my-2.5"
/>
<p class="m-0">
{{ formatMessage(messages.postExitDescription) }}
</p>
</div>
</template>
@@ -73,9 +73,9 @@ const [
])
const { data: modpackInfo } = useQuery({
queryKey: computed(() => ['linkedModpackInfo', instance.path]),
queryFn: () => get_linked_modpack_info(instance.path, 'must_revalidate'),
enabled: computed(() => !!instance.linked_data?.project_id && !offline),
queryKey: computed(() => ['linkedModpackInfo', instance.value.path]),
queryFn: () => get_linked_modpack_info(instance.value.path, 'must_revalidate'),
enabled: computed(() => !!instance.value.linked_data?.project_id && !offline),
})
const repairing = ref(false)
@@ -101,13 +101,13 @@ function getManifest(loader: string) {
provideAppBackup({
async createBackup() {
const allProfiles = await list()
const prefix = `${instance.name} - Backup #`
const prefix = `${instance.value.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.path)
const newPath = await duplicate(instance.value.path)
await edit(newPath, { name: `${prefix}${nextNum}` })
},
})
@@ -118,27 +118,30 @@ provideInstallationSettings({
const rows = [
{
label: formatMessage(commonMessages.platformLabel),
value: formatLoaderLabel(instance.loader),
value: formatLoaderLabel(instance.value.loader),
},
{
label: formatMessage(commonMessages.gameVersionLabel),
value: instance.game_version,
value: instance.value.game_version,
},
]
if (instance.loader !== 'vanilla' && instance.loader_version) {
if (instance.value.loader !== 'vanilla' && instance.value.loader_version) {
rows.push({
label: formatMessage(messages.loaderVersion, {
loader: formatLoaderLabel(instance.loader),
loader: formatLoaderLabel(instance.value.loader),
}),
value: instance.loader_version,
value: instance.value.loader_version,
})
}
return rows
}),
isLinked: computed(() => !!instance.linked_data?.locked),
isLinked: computed(() => !!instance.value.linked_data?.locked),
isBusy: computed(
() =>
instance.install_stage !== 'installed' || repairing.value || reinstalling.value || !!offline,
instance.value.install_stage !== 'installed' ||
repairing.value ||
reinstalling.value ||
!!offline,
),
modpack: computed(() => {
if (!modpackInfo.value) return null
@@ -149,9 +152,9 @@ provideInstallationSettings({
versionNumber: modpackInfo.value.version?.version_number,
}
}),
currentPlatform: computed(() => instance.loader),
currentGameVersion: computed(() => instance.game_version),
currentLoaderVersion: computed(() => instance.loader_version ?? ''),
currentPlatform: computed(() => instance.value.loader),
currentGameVersion: computed(() => instance.value.game_version),
currentLoaderVersion: computed(() => instance.value.loader_version ?? ''),
availablePlatforms: loaders?.value?.map((x) => x.name) ?? [],
resolveGameVersions(loader, showSnapshots) {
@@ -194,50 +197,50 @@ provideInstallationSettings({
if (platform !== 'vanilla' && loaderVersionId) {
editProfile.loader_version = loaderVersionId
}
await edit(instance.path, editProfile).catch(handleError)
await edit(instance.value.path, editProfile).catch(handleError)
},
afterSave: async () => {
await install(instance.path, false).catch(handleError)
await install(instance.value.path, false).catch(handleError)
trackEvent('InstanceRepair', {
loader: instance.loader,
game_version: instance.game_version,
loader: instance.value.loader,
game_version: instance.value.game_version,
})
},
async repair() {
repairing.value = true
await install(instance.path, true).catch(handleError)
await install(instance.value.path, true).catch(handleError)
repairing.value = false
trackEvent('InstanceRepair', {
loader: instance.loader,
game_version: instance.game_version,
loader: instance.value.loader,
game_version: instance.value.game_version,
})
},
async reinstallModpack() {
reinstalling.value = true
await update_repair_modrinth(instance.path).catch(handleError)
await update_repair_modrinth(instance.value.path).catch(handleError)
reinstalling.value = false
trackEvent('InstanceRepair', {
loader: instance.loader,
game_version: instance.game_version,
loader: instance.value.loader,
game_version: instance.value.game_version,
})
},
async unlinkModpack() {
await edit(instance.path, {
await edit(instance.value.path, {
linked_data: null as unknown as undefined,
})
await queryClient.invalidateQueries({
queryKey: ['linkedModpackInfo', instance.path],
queryKey: ['linkedModpackInfo', instance.value.path],
})
onUnlinked()
},
getCachedModpackVersions: () => null,
async fetchModpackVersions() {
const versions = await get_project_versions(instance.linked_data!.project_id!).catch(
const versions = await get_project_versions(instance.value.linked_data!.project_id!).catch(
handleError,
)
return (versions ?? []) as Labrinth.Versions.v2.Version[]
@@ -250,20 +253,20 @@ provideInstallationSettings({
},
async onModpackVersionConfirm(version) {
await update_managed_modrinth_version(instance.path, version.id)
await update_managed_modrinth_version(instance.value.path, version.id)
await queryClient.invalidateQueries({
queryKey: ['linkedModpackInfo', instance.path],
queryKey: ['linkedModpackInfo', instance.value.path],
})
},
updaterModalProps: computed(() => ({
isApp: true,
currentVersionId:
modpackInfo.value?.update_version_id ?? instance.linked_data?.version_id ?? '',
modpackInfo.value?.update_version_id ?? instance.value.linked_data?.version_id ?? '',
projectIconUrl: modpackInfo.value?.project?.icon_url,
projectName: modpackInfo.value?.project?.title ?? 'Modpack',
currentGameVersion: instance.game_version,
currentLoader: instance.loader,
currentGameVersion: instance.value.game_version,
currentLoader: instance.value.loader,
})),
isServer: false,
@@ -25,20 +25,24 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
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 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 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.custom_env_vars?.length ?? 0) > 0)
const envVars = ref(
(instance.custom_env_vars ?? globalSettings.custom_env_vars).map((x) => x.join('=')).join(' '),
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 overrideMemorySettings = ref(!!instance.memory)
const memory = ref(instance.memory ?? globalSettings.memory)
const overrideEnvVars = ref((instance.value.custom_env_vars?.length ?? 0) > 0)
const envVars = ref(
(instance.value.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 { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
maxMemory: number
snapPoints: number[]
@@ -76,7 +80,7 @@ watch(
memory,
],
async () => {
await edit(instance.path, editProfileObject.value)
await edit(instance.value.path, editProfileObject.value)
},
{ deep: true },
)
@@ -107,10 +111,10 @@ const messages = defineMessages({
<template>
<div>
<h2 id="project-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
<h2 id="project-name" class="m-0 mb-2.5 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.javaInstallation) }}
</h2>
<Checkbox v-model="overrideJavaInstall" label="Custom Java installation" class="mb-2" />
<Checkbox v-model="overrideJavaInstall" label="Custom Java installation" class="mb-2.5" />
<template v-if="!overrideJavaInstall">
<div class="flex my-2 items-center gap-2 font-semibold">
<template v-if="javaInstall">
@@ -140,10 +144,10 @@ const messages = defineMessages({
</div>
</template>
<JavaSelector v-if="overrideJavaInstall || !javaInstall" v-model="javaInstall" />
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
<h2 id="project-name" class="mt-6 mb-2.5 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.javaMemory) }}
</h2>
<Checkbox v-model="overrideMemorySettings" label="Custom memory allocation" class="mb-2" />
<Checkbox v-model="overrideMemorySettings" label="Custom memory allocation" class="mb-2.5" />
<Slider
id="max-memory"
v-model="memory.maximum"
@@ -155,7 +159,7 @@ const messages = defineMessages({
:snap-range="512"
unit="MB"
/>
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
<h2 id="project-name" class="mt-6 mb-2.5 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.javaArguments) }}
</h2>
<Checkbox v-model="overrideJavaArgs" label="Custom java arguments" class="my-2" />
@@ -167,10 +171,10 @@ const messages = defineMessages({
placeholder="Enter java arguments..."
wrapper-class="w-full"
/>
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
<h2 id="project-name" class="mt-6 mb-2.5 text-lg font-semibold text-contrast block">
{{ formatMessage(messages.javaEnvironmentVariables) }}
</h2>
<Checkbox v-model="overrideEnvVars" label="Custom environment variables" class="mb-2" />
<Checkbox v-model="overrideEnvVars" label="Custom environment variables" class="mb-2.5" />
<StyledInput
id="env-vars"
v-model="envVars"
@@ -22,12 +22,14 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideWindowSettings = ref(!!instance.game_resolution || !!instance.force_fullscreen)
const overrideWindowSettings = ref(
!!instance.value.game_resolution || !!instance.value.force_fullscreen,
)
const resolution: Ref<[number, number]> = ref(
instance.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]),
instance.value.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]),
)
const fullscreenSetting: Ref<boolean> = ref(
instance.force_fullscreen ?? globalSettings.force_fullscreen,
instance.value.force_fullscreen ?? globalSettings.force_fullscreen,
)
const editProfileObject = computed(() => {
@@ -46,7 +48,7 @@ const editProfileObject = computed(() => {
watch(
[overrideWindowSettings, resolution, fullscreenSetting],
async () => {
await edit(instance.path, editProfileObject.value)
await edit(instance.value.path, editProfileObject.value)
},
{ deep: true },
)
@@ -92,14 +94,14 @@ const messages = defineMessages({
</script>
<template>
<div>
<div class="flex flex-col gap-6">
<Checkbox
v-model="overrideWindowSettings"
:label="formatMessage(messages.customWindowSettings)"
/>
<div class="mt-2 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
<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">
{{ formatMessage(messages.fullscreen) }}
</h2>
<p class="m-0">
@@ -118,9 +120,9 @@ const messages = defineMessages({
/>
</div>
<div class="mt-4 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
<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">
{{ formatMessage(messages.width) }}
</h2>
<p class="m-0">
@@ -137,9 +139,9 @@ const messages = defineMessages({
/>
</div>
<div class="mt-4 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
<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">
{{ formatMessage(messages.height) }}
</h2>
<p class="m-0">
@@ -6,12 +6,13 @@ import {
LanguagesIcon,
ModrinthIcon,
PaintbrushIcon,
ReportIcon,
SettingsIcon,
ShieldIcon,
ToggleRightIcon,
} from '@modrinth/assets'
import {
commonMessages,
commonSettingsMessages,
defineMessage,
defineMessages,
ProgressBar,
@@ -95,11 +96,8 @@ const tabs = [
content: ResourceManagementSettings,
},
{
name: defineMessage({
id: 'app.settings.tabs.feature-flags',
defaultMessage: 'Feature flags',
}),
icon: ReportIcon,
name: commonSettingsMessages.featureFlags,
icon: ToggleRightIcon,
content: FeatureFlagSettings,
developerOnly: true,
},
@@ -179,7 +177,7 @@ const messages = defineMessages({
>
<ModrinthIcon class="w-6 h-6" />
</button>
<div>
<div class="max-w-[200px]">
<p class="m-0">Modrinth App {{ version }}</p>
<p class="m-0">
<span v-if="osPlatform === 'macos'">macOS</span>
@@ -44,8 +44,10 @@ const emit = defineEmits<{
const isMinecraftServer = ref(false)
const handleUnlinked = () => emit('unlinked')
const instanceRef = computed(() => props.instance)
provideInstanceSettings({
instance: props.instance,
instance: instanceRef,
offline: props.offline,
isMinecraftServer,
onUnlinked: handleUnlinked,
@@ -1,21 +1,31 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.header)" fade="warning" max-width="500px">
<Admonition type="warning" :header="formatMessage(messages.admonitionHeader)">
{{ formatMessage(messages.admonitionBody, { instanceName }) }}
</Admonition>
<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>
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button class="!border !border-surface-4" @click="handleGoToInstance">
<ExternalIcon />
{{ formatMessage(messages.goToInstance) }}
<button class="!border !border-surface-4" @click="handleCancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="handleGoToInstance">
{{ formatMessage(messages.instance) }}
<RightArrowIcon />
</button>
</ButtonStyled>
<ButtonStyled color="orange">
<button @click="handleCreateAnyway">
<PlusIcon />
{{ formatMessage(messages.createAnyway) }}
{{ formatMessage(messages.create) }}
</button>
</ButtonStyled>
</div>
@@ -24,8 +34,15 @@
</template>
<script setup lang="ts">
import { ExternalIcon, PlusIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
import { PlusIcon, RightArrowIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
commonMessages,
defineMessages,
IntlFormatted,
NewModal,
useVIntl,
} from '@modrinth/ui'
import { ref } from 'vue'
const { formatMessage } = useVIntl()
@@ -35,21 +52,18 @@ const messages = defineMessages({
id: 'app.instance.modpack-already-installed.header',
defaultMessage: 'Modpack already installed',
},
admonitionHeader: {
id: 'app.instance.modpack-already-installed.admonition-header',
defaultMessage: 'Duplicate modpack',
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?',
},
admonitionBody: {
id: 'app.instance.modpack-already-installed.admonition-body',
defaultMessage: 'This modpack is already installed in the "{instanceName}" instance.',
instance: {
id: 'app.instance.modpack-already-installed.instance',
defaultMessage: 'Instance',
},
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',
create: {
id: 'app.instance.modpack-already-installed.create',
defaultMessage: 'Create',
},
})
@@ -68,6 +82,10 @@ 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-extrabold text-contrast">Color theme</h2>
<h2 class="m-0 text-lg font-semibold 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-4 flex items-center justify-between">
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Advanced rendering</h2>
<h2 class="m-0 text-lg font-semibold 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-4 flex items-center justify-between">
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Hide nametag</h2>
<h2 class="m-0 text-lg font-semibold 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-4 flex items-center justify-between gap-4">
<div v-if="os !== 'MacOS'" class="mt-6 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Native decorations</h2>
<h2 class="m-0 text-lg font-semibold 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-4 flex items-center justify-between">
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Minimize launcher</h2>
<h2 class="m-0 text-lg font-semibold 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-4 flex items-center justify-between">
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Default landing page</h2>
<h2 class="m-0 text-lg font-semibold 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="w-40"
class="max-w-40"
:options="['Home', 'Library'].map((v) => ({ value: v, label: v }))"
:display-value="settings.default_page ?? 'Select an option'"
/>
</div>
<div class="mt-4 flex items-center justify-between">
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Jump back into worlds</h2>
<h2 class="m-0 text-lg font-semibold 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-4 flex items-center justify-between">
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Toggle sidebar</h2>
<h2 class="m-0 text-lg font-semibold text-contrast">Toggle sidebar</h2>
<p class="m-0 mt-1">Enables the ability to toggle the sidebar.</p>
</div>
<Toggle
@@ -52,127 +52,135 @@ watch(
<template>
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Window size</h2>
<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>
<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>
<Toggle id="fullscreen" v-model="settings.force_fullscreen" />
</div>
<Toggle id="fullscreen" v-model="settings.force_fullscreen" />
</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>
<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>
<StyledInput
id="width"
v-model="settings.game_resolution[0]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter width..."
/>
</div>
<StyledInput
id="width"
v-model="settings.game_resolution[0]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter width..."
/>
<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>
</div>
<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>
<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>
<StyledInput
id="height"
v-model="settings.game_resolution[1]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter height..."
/>
<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>
</div>
<hr class="mt-4 bg-button-border border-none h-[1px]" />
<hr class="my-6 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"
/>
<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>
<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"
/>
<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>
<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 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>
</div>
</div>
</template>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Toggle } from '@modrinth/ui'
import { ButtonStyled, Toggle } from '@modrinth/ui'
import { ref, watch } from 'vue'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
@@ -25,17 +25,28 @@ watch(
)
</script>
<template>
<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 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>
<Toggle
id="advanced-rendering"
:model-value="themeStore.getFeatureFlag(option)"
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
/>
</div>
</template>
@@ -21,15 +21,21 @@ async function updateJavaVersion(version) {
}
</script>
<template>
<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 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>
</template>
@@ -43,7 +43,7 @@ async function onLocaleChange(newLocale: string) {
</script>
<template>
<h2 class="m-0 text-lg font-extrabold text-contrast">Language</h2>
<h2 class="m-0 text-lg font-semibold 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-extrabold text-contrast">Personalized ads</h2>
<p class="m-0 text-sm">
<h2 class="m-0 text-lg font-semibold text-contrast">Personalized ads</h2>
<p class="m-0 mt-1 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-extrabold text-contrast">Telemetry</h2>
<p class="m-0 text-sm">
<h2 class="m-0 text-lg font-semibold text-contrast">Telemetry</h2>
<p class="m-0 mt-1 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-extrabold text-contrast">Discord RPC</h2>
<p class="m-0 text-sm">
<h2 class="m-0 text-lg font-semibold text-contrast">Discord RPC</h2>
<p class="m-0 mt-1 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,67 +62,77 @@ async function findLauncherDir() {
</script>
<template>
<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-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>
<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 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>
<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 () => {
})
})
populateJumpBackIn()
await populateJumpBackIn()
.catch(() => {
console.error('Failed to populate jump back in')
})
@@ -189,6 +189,22 @@ 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>
@@ -243,13 +259,17 @@ const messages = defineMessages({
>
<template v-if="refreshing">
<SpinnerIcon aria-hidden="true" class="animate-spin shrink-0" />
Loading...
{{ formatMessage(commonMessages.loadingLabel) }}
</template>
<template v-else-if="serverStatus">
<template v-if="serverIncompatible">
<IssuesIcon class="shrink-0 text-orange" aria-hidden="true" />
<span class="text-orange">
Incompatible version {{ serverStatus.version?.name }}
{{
formatMessage(messages.incompatibleVersion, {
version: serverStatus.version?.name,
})
}}
</span>
</template>
<template v-else>
@@ -265,8 +285,11 @@ const messages = defineMessages({
/>
<Tooltip :disabled="!hasPlayersTooltip">
<span :class="{ 'cursor-help': hasPlayersTooltip }">
{{ formatNumber(serverStatus.players?.online) }}
online
{{
formatMessage(messages.playersOnline, {
count: formatNumber(serverStatus.players?.online ?? 0),
})
}}
</span>
<template #popper>
<div class="flex flex-col gap-1">
@@ -280,7 +303,7 @@ const messages = defineMessages({
</template>
<template v-else>
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" />
Offline
{{ formatMessage(messages.offline) }}
</template>
</div>
</div>
@@ -299,7 +322,7 @@ const messages = defineMessages({
})
}}
</template>
<template v-else> Not played yet </template>
<template v-else> {{ formatMessage(messages.notPlayedYet) }} </template>
</div>
<template v-if="instancePath">
@@ -5,12 +5,11 @@ 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'
@@ -26,10 +25,10 @@ const props = defineProps<{
instance: GameInstance
}>()
const modal = ref()
const modal = ref<InstanceType<typeof NewModal>>()
const name = ref()
const address = ref()
const name = ref('')
const address = ref('')
const resourcePack = ref<ServerPackStatus>('enabled')
async function addServer(play: boolean) {
@@ -60,11 +59,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({
@@ -85,37 +84,33 @@ const messages = defineMessages({
defineExpose({ show, hide })
</script>
<template>
<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>
<NewModal ref="modal" :header="formatMessage(messages.title)" width="500px" max-width="500px">
<ServerModalBody
v-model:name="name"
v-model:address="address"
v-model:resource-pack="resourcePack"
/>
<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 #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>
</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()
const modal = ref<InstanceType<typeof NewModal>>()
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,29 +96,28 @@ const titleMessage = defineMessage({
})
</script>
<template>
<ModalWrapper ref="modal">
<template #title>
<span class="font-extrabold text-lg text-contrast">{{ formatMessage(titleMessage) }}</span>
</template>
<NewModal ref="modal" :header="formatMessage(titleMessage)" width="500px" max-width="500px">
<ServerModalBody
v-model:name="name"
v-model:address="address"
v-model:resource-pack="resourcePack"
/>
<HideFromHomeOption v-model="hideFromHome" class="mt-3" />
<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 #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>
</template>
@@ -49,34 +49,40 @@ 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="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>
<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>
<Combobox
v-model="resourcePack"
:options="
@@ -89,9 +95,9 @@ defineExpose({ resourcePackOptions })
:display-value="
resourcePack
? formatMessage(resourcePackOptionMessages[resourcePack])
: 'Select an option'
: formatMessage(messages.selectAnOption)
"
/>
</div>
</label>
</div>
</template>
@@ -0,0 +1,99 @@
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
@@ -0,0 +1,18 @@
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,
}
@@ -0,0 +1,35 @@
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,3 +97,23 @@ 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,3 +63,13 @@ 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,6 +30,8 @@ export type ServerWorld = BaseWorld & {
index: number
address: string
pack_status: ServerPackStatus
project_id?: string
content_kind?: string
}
export type World = SingleplayerWorld | ServerWorld
@@ -140,12 +142,16 @@ 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,
})
}
+120 -51
View File
@@ -5,8 +5,41 @@
"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": "تصدير"
@@ -30,49 +63,37 @@
"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.admonition-body": {
"message": "تم تثبيت حزمة التعديل هذه بالفعل في مثيل \"{instanceName}\"."
"app.instance.modpack-already-installed.body": {
"message": "حُزْمَة التعديل هذه مثبته فعلًا في نموذج <bold>{instanceName}</bold>. هل انت متأكد بإرادة نسخه؟"
},
"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.create": {
"message": "إنشاء"
},
"app.instance.modpack-already-installed.header": {
"message": "تم تثبيت حزمة التعديل بالفعل"
"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} من المشاريع"
"message": "تمت إضافة {count} مشروع"
},
"app.instance.mods.share-text": {
"message": "تحقق من المشاريع التي أستخدمها في حزمة التعديل الخاص بي!"
@@ -80,17 +101,53 @@
"app.instance.mods.share-title": {
"message": "مشاركة محتوى حزمة التعديل"
},
"app.instance.mods.show-file": {
"message": "عرض الملف"
},
"app.instance.mods.successfully-uploaded": {
"message": "تم الرفع بنجاح"
},
"app.instance.mods.unknown-version": {
"message": "غير معروف"
"app.instance.worlds.add-server": {
"message": "أضف خادم"
},
"app.instance.mods.updating": {
"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": "المحتوى مطلوب"
@@ -102,7 +159,7 @@
"message": "تثبيت"
},
"app.modal.install-to-play.mod-count": {
"message": ""
"message": "{count, plural, one {# تعديل} other {# تعديلات}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "حزمة التعديل مطلوبة"
@@ -111,10 +168,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": "عرض المحتويات"
@@ -126,7 +183,7 @@
"message": "يلزم التحديث"
},
"app.modal.update-to-play.update-required-description": {
"message": "هناك تحديث لازم للعب بـ {name}. الرجاء التحديث إلى آخر اصدار لتشغيل اللعبة."
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى آخر اصدار لتشغيل اللعبة."
},
"app.settings.developer-mode-enabled": {
"message": "تم تفعيل وضع المطوّر."
@@ -140,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "خيارات النسخة الافتراضية"
},
"app.settings.tabs.feature-flags": {
"message": "أعلام الميزات"
},
"app.settings.tabs.java-installations": {
"message": "تثبيتات جافا"
},
@@ -197,6 +251,24 @@
"app.update.reload-to-update": {
"message": "أعد التحميل لتثبيت التحديث"
},
"app.world.server-modal.placeholder-address": {
"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": "إضافة صديق"
},
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "تعديل العالم"
},
"instance.files.adding-files": {
"message": "إضافة ملفات ({completed}\\{total})"
},
"instance.files.save-as": {
"message": "حفظ ك..."
},
"instance.server-modal.address": {
"message": "العنوان"
},
@@ -333,7 +411,7 @@
"message": "نسخة النسخة"
},
"instance.settings.tabs.general.duplicate-instance.description": {
"message": "إنشاء."
"message": "ينشئ نسخة من هذا النموذج شاملا عوالمه, تعديلاتها النصيه, إلخ..."
},
"instance.settings.tabs.general.edit-icon": {
"message": "تعديل الأيقونة"
@@ -467,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "عدم العرض في الصفحة الرئيسية"
},
"instance.worlds.filter.available": {
"message": "متاح"
},
"instance.worlds.game_already_open": {
"message": "النسخة مفتوحة بالفعل"
},
@@ -494,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "تشغيل النسخة"
},
"instance.worlds.type.server": {
"message": "الخادم"
},
"instance.worlds.type.singleplayer": {
"message": "لعب فردي"
},
"instance.worlds.view_instance": {
"message": "عرض النسخة"
},
@@ -522,7 +591,7 @@
"message": "يقدمها الخادم"
},
"search.filter.locked.server-environment.title": {
"message": "يمكن إضافة التعديلات من جانب العميل فقط إلى مثيل الخادم"
"message": "يمكن إضافة التعديلات **المحليه** فقط إلى نموذج الخادم"
},
"search.filter.locked.server-game-version.title": {
"message": "يتم توفير نسخة اللعبة من قبل الخادم"
+137 -14
View File
@@ -5,12 +5,147 @@
"app.auth-servers.unreachable.header": {
"message": "Připojení k autorizačním serverům se nezdařilo"
},
"app.browse.add-server-to-instance": {
"message": "Přidat server do instance"
},
"app.browse.add-servers-to-instance": {
"message": "Přidat servery do instance"
},
"app.browse.add-to-instance": {
"message": "Přidat do instance"
},
"app.browse.add-to-instance-name": {
"message": "Přidat do {instanceName}"
},
"app.browse.added": {
"message": "Přidáno"
},
"app.browse.already-added": {
"message": "Už přidáno"
},
"app.browse.discover-content": {
"message": "Prozkoumat obsah"
},
"app.browse.discover-servers": {
"message": "Prozkoumat servery"
},
"app.browse.hide-added-servers": {
"message": "Skrýt přidané servery"
},
"app.browse.hide-installed-content": {
"message": "Skrýt nainstalovaný obsah"
},
"app.browse.install-content-to-instance": {
"message": "Nainstalovat obsah do instnce"
},
"app.export-modal.description-placeholder": {
"message": "Přidej popis modpacku..."
},
"app.export-modal.export-button": {
"message": "Exportovat"
},
"app.export-modal.header": {
"message": "Exportovat modpack"
},
"app.export-modal.modpack-name-label": {
"message": "Jméno Modpacku"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Jméno modpacku"
},
"app.export-modal.select-files-label": {
"message": "Vybrat soubory a složky co zahrnout do modpacku"
},
"app.export-modal.version-number-label": {
"message": "Číslo verze"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Všechna data z instance budou navždy smazána, včetně světů, nastavení a všeho ostatního."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Tato akce nemůže být vrácena"
},
"app.instance.confirm-delete.delete-button": {
"message": "Odstranit instanci"
},
"app.instance.confirm-delete.header": {
"message": "Odstanit instanci"
},
"app.instance.modpack-already-installed.body": {
"message": "Tento modpack je už nainstalovaný v instanci <bold>{instanceName}</bold>. Opravdu ho chceš duplikovat?"
},
"app.instance.modpack-already-installed.create": {
"message": "Vytvořit"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack už je nainstalovaný"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" bylo přidáno"
},
"app.instance.mods.projects-were-added": {
"message": "{count} projektů bylo přidáno"
},
"app.instance.mods.share-text": {
"message": "Podívejte se na projekty co používám ve svém modpacku!"
},
"app.instance.mods.share-title": {
"message": "Sdílení obsahu modpacku"
},
"app.instance.mods.successfully-uploaded": {
"message": "Úspěšně nahráno"
},
"app.instance.worlds.add-server": {
"message": "Přidat server"
},
"app.instance.worlds.browse-servers": {
"message": "Procházet servery"
},
"app.instance.worlds.delete-world-description": {
"message": "\"{name}\" bude **navždy smazáno** a nebude způsob, jak to obnovit."
},
"app.instance.worlds.delete-world-title": {
"message": "Opravdu si jsi jistý, že chceš navždy smazat tenhle svět?"
},
"app.instance.worlds.filter-modded": {
"message": "Modováno"
},
"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": "Přidej nebo procházej servery"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Žádné servery nebo světy nebyly přidány"
},
"app.instance.worlds.remove-server-description": {
"message": "„{name}“ bude odstraněn z tvého seznamu, včetně hry, a nebude žádný způsob, jak ho obnovit."
},
"app.instance.worlds.remove-server-title": {
"message": "Opravdu chceš odstranit {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hledat ve světech {count}..."
},
"app.instance.worlds.this-server": {
"message": "tento server"
},
"app.modal.install-to-play.header": {
"message": "Nainstaluj ke hraní"
},
@@ -47,9 +182,6 @@
"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"
},
@@ -84,7 +216,7 @@
"message": "Přidat přítele"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, one {žádost} few {žádosti} other {žádostí}} o přátelství"
"message": "{count} přátelé {count, plural, one {request} other {requests}}"
},
"friends.add-friend.submit": {
"message": "Poslat žádost o přátelství"
@@ -350,9 +482,6 @@
"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á"
},
@@ -369,17 +498,11 @@
"message": "Můžeš rovnou skočit na server pouze v Minecraftu Alpha 1.0.5+"
},
"instance.worlds.no_singleplayer_quick_play": {
"message": "Můžeš se rovnou připojit do světa jednoho hráče pouze v Minecraftu 1.20+"
"message": "V Minecraftu 1.20+ se rovnou přeskočit pouze do singleplayerových světů"
},
"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"
},
+27 -30
View File
@@ -5,6 +5,27 @@
"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..."
},
@@ -41,18 +62,15 @@
"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"
},
@@ -65,18 +83,9 @@
"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"
},
@@ -125,9 +134,6 @@
"app.settings.tabs.default-instance-options": {
"message": "Standardindstillinger for instans"
},
"app.settings.tabs.feature-flags": {
"message": "Funktionsflags"
},
"app.settings.tabs.java-installations": {
"message": "Javainstallationer"
},
@@ -452,9 +458,6 @@
"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"
},
@@ -479,12 +482,6 @@
"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"
},
+107 -38
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Instanz löschen"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Dieses Modpack ist bereits in der Instanz {instanceName} installiert."
"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-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.create": {
"message": "Erstellen"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Unbekannt"
"app.instance.worlds.add-server": {
"message": "Server hinzufügen"
},
"app.instance.mods.updating": {
"message": "Wird aktualisiert..."
"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.modal.install-to-play.content-required": {
"message": "Inhalte benötigt"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+108 -39
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -30,7 +63,7 @@
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten für deine Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
"message": "Alle Daten deiner Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Diese Aktion kann nicht rückgängig gemacht werden"
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Instanz löschen"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Dieses Modpack ist bereits in der Instanz {instanceName} installiert."
"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-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.create": {
"message": "Erstellen"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Unbekannt"
"app.instance.worlds.add-server": {
"message": "Server hinzufügen"
},
"app.instance.mods.updating": {
"message": "Wird aktualisiert..."
"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.modal.install-to-play.content-required": {
"message": "Inhalte benötigt"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+107 -38
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Delete instance"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "This modpack is already installed in the \"{instanceName}\" 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-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.create": {
"message": "Create"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Unknown"
"app.instance.worlds.add-server": {
"message": "Add server"
},
"app.instance.mods.updating": {
"message": "Updating..."
"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.modal.install-to-play.content-required": {
"message": "Content required"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+119 -50
View File
@@ -5,8 +5,41 @@
"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": "Escribe la descripción del modpack..."
"message": "Introduce la descripción del modpack..."
},
"app.export-modal.export-button": {
"message": "Exportar"
@@ -21,16 +54,16 @@
"message": "Nombre del modpack"
},
"app.export-modal.select-files-label": {
"message": "Seleccione archivos y carpetas para incluir en el paquete"
"message": "Selecciona archivos y carpetas para incluirlos en el pack"
},
"app.export-modal.version-number-label": {
"message": "Número de versión"
"message": "Número de la versión"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos los datos de su instancia se eliminarán permanentemente, incluidos sus mundos, configuraciones y todo el contenido instalado."
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Esta acción no se puede deshacer"
@@ -41,56 +74,80 @@
"app.instance.confirm-delete.header": {
"message": "Eliminar instancia"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Este modpack ya está instalado en la instancia \"{instanceName}\"."
"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-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.create": {
"message": "Crear"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack ya está instalado"
"message": "Este 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}\""
"message": "Se añadió \"{name}\""
},
"app.instance.mods.projects-were-added": {
"message": "Se han añadido {count} proyectos"
"message": "Se añadieron {count} proyectos"
},
"app.instance.mods.share-text": {
"message": Echa un vistazo a los proyectos que utilizo en mi modpack!"
"message": Mira a los proyectos que estoy usando en mi modpack!"
},
"app.instance.mods.share-title": {
"message": "Compartir contenido del modpack"
},
"app.instance.mods.show-file": {
"message": "Mostrar archivo"
"message": "Compartiendo contenido del modpack"
},
"app.instance.mods.successfully-uploaded": {
"message": "Se ha subido correctamente"
"message": "Cargado correctamente"
},
"app.instance.mods.unknown-version": {
"message": "Desconocido"
"app.instance.worlds.add-server": {
"message": "Añadir servidor"
},
"app.instance.mods.updating": {
"message": "Actualizando..."
"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.modal.install-to-play.content-required": {
"message": "Contenido requerido"
@@ -108,7 +165,7 @@
"message": "Modpack requerido"
},
"app.modal.install-to-play.server-requires-mods": {
"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."
"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."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instancia compartida"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -213,7 +285,7 @@
"message": "¡Podría ser distinto a su nombre de usuario de Minecraft!"
},
"friends.add-friend.username.placeholder": {
"message": "Ingresa tu nombre de usuario de Modrinth..."
"message": "Introduce el nombre de usuario de Modrinth..."
},
"friends.add-friend.username.title": {
"message": "¿Cuál es el nombre de usuario de Modrinth de tu amigo?"
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+107 -38
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Eliminar instancia"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Este modpack ya está instalado en la instancia \"{instanceName}\"."
"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-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.create": {
"message": "Crear"
},
"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}\""
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Desconocido"
"app.instance.worlds.add-server": {
"message": "Añadir servidor"
},
"app.instance.mods.updating": {
"message": "Actualizando..."
"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.modal.install-to-play.content-required": {
"message": "Contenido obligatorio"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+150 -15
View File
@@ -5,8 +5,53 @@
"app.auth-servers.unreachable.header": {
"message": "Todennuspalvelimiin ei saada yhteyttä"
},
"app.browse.add-server-to-instance": {
"message": "Lisää palvelin instanssiin"
},
"app.browse.add-servers-to-instance": {
"message": "Lisää palvelimia instanssiisi"
},
"app.browse.add-to-instance": {
"message": "Lisää instanssiin"
},
"app.browse.add-to-instance-name": {
"message": "Lisää instanssiin {instanceName}"
},
"app.browse.added": {
"message": "Lisätty"
},
"app.browse.already-added": {
"message": "On jo asennettu"
},
"app.browse.discover-content": {
"message": "Löydä sisältöä"
},
"app.browse.discover-servers": {
"message": "Löydä palvelimia"
},
"app.browse.hide-added-servers": {
"message": "Piilota lisätyt palvelimet"
},
"app.browse.hide-installed-content": {
"message": "Piilota asennettu sisältö"
},
"app.browse.install-content-to-instance": {
"message": "Lataa sisältöä instanssiin"
},
"app.export-modal.description-placeholder": {
"message": "Lisää modipaketin kuvaus..."
},
"app.export-modal.export-button": {
"message": "Vie"
},
"app.export-modal.header": {
"message": "Vie modipaketti"
},
"app.export-modal.modpack-name-label": {
"message": "Modipaketin nimi"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack nimi"
"message": "Modipaketin nimi"
},
"app.export-modal.select-files-label": {
"message": "Valitse tiedostot ja kansiot pakettiin"
@@ -17,12 +62,90 @@
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Kaikki sinun instanssisi data poistetaan pysyvästi, sisältäen maailmasi, konfiguraatiosi ja asennetun sisällön."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Tätä toimintoa ei voi peruuttaa"
},
"app.instance.confirm-delete.delete-button": {
"message": "Poista instannsi"
},
"app.instance.confirm-delete.header": {
"message": "Poista instanssi"
},
"app.instance.modpack-already-installed.body": {
"message": "Tämä modipaketti on jo asennettu instanssiin <bold>{instanceName}</bold>. Oletko varma että haluat kopioida sen?"
},
"app.instance.modpack-already-installed.create": {
"message": "Luo"
},
"app.instance.modpack-already-installed.header": {
"message": "Modipaketti on jo asennettu"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instanssi"
},
"app.instance.mods.content-type-project": {
"message": "projekti"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" lisättiin"
},
"app.instance.mods.projects-were-added": {
"message": "{count} projektia lisättiin"
},
"app.instance.mods.share-text": {
"message": "Tsekkaa projektit joita käytän minun modipaketissani!"
},
"app.instance.mods.successfully-uploaded": {
"message": "Onnistuneesti ladattu"
},
"app.instance.worlds.add-server": {
"message": "Lisää palvelin"
},
"app.instance.worlds.browse-servers": {
"message": "Selaa palvelimia"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' tullaan **poistamaan pysyvästi**, eikä tule olemaan mitään tapaa palauttaa sitä."
},
"app.instance.worlds.delete-world-title": {
"message": "Oletko varma että haluat pysyvästi poistaa tämän maailman?"
},
"app.instance.worlds.filter-modded": {
"message": "Modattu"
},
"app.instance.worlds.filter-offline": {
"message": "Offline-tilassa"
},
"app.instance.worlds.filter-online": {
"message": "Online-tilassa"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.no-worlds-description": {
"message": "Lisää palvelin tai selaa aloittaaksesi"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Ei palvelimia tai maailmoja lisättynä"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' tullaan poistamaan sinun listaltasi, mukaan lukien pelin sisällä, eikä tule olemaan mitään tapaa palauttaa sitä."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) tullaan poistamaan sinun listaltasi, mukaan lukien pelin sisällä, eikä tule olemaan mitään tapaa palauttaa sitä."
},
"app.instance.worlds.remove-server-title": {
"message": "Oletko varma että haluat poistaa {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hae {count} maailmasta..."
},
"app.instance.worlds.this-server": {
"message": "tämän palvelimen"
},
"app.modal.install-to-play.content-required": {
"message": "Sisältö vaaditaan"
},
@@ -33,7 +156,7 @@
"message": "Asenna"
},
"app.modal.install-to-play.mod-count": {
"message": "one {{count, plural, one {# mod} other {# modia}}"
"message": "{count, plural, one {# mod} other {# modia}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Vaadittu modipaketti"
@@ -71,9 +194,6 @@
"app.settings.tabs.default-instance-options": {
"message": "Oletusinstanssin asetukset"
},
"app.settings.tabs.feature-flags": {
"message": "Ominaisuuskytkimet"
},
"app.settings.tabs.java-installations": {
"message": "Java asennukset"
},
@@ -128,6 +248,24 @@
"app.update.reload-to-update": {
"message": "Lataa uudelleen asentaaksesi päivityksen"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Valitse vaihtoehto"
},
"app.world.world-item.incompatible-version": {
"message": "Yhteensopimaton versio {version}"
},
"app.world.world-item.not-played-yet": {
"message": "Ei pelattu vielä"
},
"app.world.world-item.offline": {
"message": "Offline-tilassa"
},
"app.world.world-item.players-online": {
"message": "{count} paikalla"
},
"friends.action.add-friend": {
"message": "Lisää ystävä"
},
@@ -227,6 +365,12 @@
"instance.edit-world.title": {
"message": "Muokkaa maailmaa"
},
"instance.files.adding-files": {
"message": "Lisätään tiedostoja ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Tallenna nimellä..."
},
"instance.server-modal.address": {
"message": "Osoite"
},
@@ -398,9 +542,6 @@
"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"
},
@@ -425,12 +566,6 @@
"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"
},
@@ -453,7 +588,7 @@
"message": "Palvelimen tarjoama"
},
"search.filter.locked.server-environment.title": {
"message": "Voit lisätä vain paikallisia modeja palvelinasennukseen"
"message": "Voit lisätä vain paikallisia modeja palvelin instanssiin"
},
"search.filter.locked.server-game-version.title": {
"message": "Peliversio on palvelimen tarjoama"
+106 -25
View File
@@ -5,38 +5,119 @@
"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.unknown-version": {
"message": "Hindi kilala"
"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.worlds.remove-server-title": {
"message": "Sigurado ka bang gusto mong tanggalin ang {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hanapin sa {count} mundo..."
},
"app.instance.worlds.this-server": {
"message": "server na ito"
},
"app.modal.install-to-play.content-required": {
"message": "Nangangailangan ng kontento"
@@ -75,7 +156,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": "Nakabukas ang moda ng nagdidibelop."
"message": "Binuksan ang moda ng nagdi-develop."
},
"app.settings.downloading": {
"message": "Dina-download ang v{version}"
@@ -86,9 +167,6 @@
"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"
},
@@ -143,6 +221,18 @@
"app.update.reload-to-update": {
"message": "Handang ma-install ang update"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Pumili ng opsiyon"
},
"app.world.world-item.incompatible-version": {
"message": "Di-magkatugmang bersiyong {version}"
},
"app.world.world-item.offline": {
"message": "Offline"
},
"friends.action.add-friend": {
"message": "Magdagdag ng kaibigan"
},
@@ -213,10 +303,10 @@
"message": "Idagdag ang server"
},
"instance.add-server.resource-pack.disabled": {
"message": "Hindi pinahihintulotan"
"message": "Tanggihan"
},
"instance.add-server.resource-pack.enabled": {
"message": "Pinahihintulotan"
"message": "Payagan"
},
"instance.add-server.resource-pack.prompt": {
"message": "Magpahintulot"
@@ -228,7 +318,7 @@
"message": "Baguhin ang server"
},
"instance.edit-world.hide-from-home": {
"message": "Huwag ipakita sa Home na pahina"
"message": "Itago sa pahina ng Tahanan"
},
"instance.edit-world.name": {
"message": "Pangalan"
@@ -411,10 +501,7 @@
"message": "Kopyahin ang adres"
},
"instance.worlds.dont_show_on_home": {
"message": "Huwag ipakita sa Home"
},
"instance.worlds.filter.available": {
"message": "Magagamit"
"message": "Huwag ipakita sa Tahanan"
},
"instance.worlds.game_already_open": {
"message": "Bukas naman ang instansiya"
@@ -440,12 +527,6 @@
"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"
},
+110 -41
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -30,7 +63,7 @@
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Toutes les données pour votre instance seront supprimée à jamais, y comprit vos mondes, configurations, et contenu supprimé."
"message": "Toutes les données pour votre instance seront supprimées à jamais, y compris vos mondes, vos configurations, et le contenu installé."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Cette action est irréversible"
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Supprimer l'instance"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Ce modpack est déjà installé dans linstance « {instanceName} »."
"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-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.create": {
"message": "Créer"
},
"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é"
},
@@ -78,19 +99,55 @@
"message": "Jette un coup d'œil aux projets que j'utilise dans mon modpack !"
},
"app.instance.mods.share-title": {
"message": "Partagez le contenu de votre modpack"
},
"app.instance.mods.show-file": {
"message": "Montrer le fichier"
"message": "Partage du contenu de votre modpack"
},
"app.instance.mods.successfully-uploaded": {
"message": "Mis en ligne avec succès"
},
"app.instance.mods.unknown-version": {
"message": "Inconnu"
"app.instance.worlds.add-server": {
"message": "Ajouter le serveur"
},
"app.instance.mods.updating": {
"message": "Mise à jour..."
"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.modal.install-to-play.content-required": {
"message": "Contenu requis"
@@ -140,9 +197,6 @@
"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"
},
@@ -156,7 +210,7 @@
"message": "Gestion des ressources"
},
"app.update-popup.body": {
"message": "Modrinth App v{version} est prête à être installée ! Rechargez pour mettre à jour maintenant, ou automatiquement quand vous fermez Modrinth App."
"message": "Modrinth App v{version} est prête à être installée ! Relancez l'application pour faire la mise à jour maintenant, ou automatiquement à la fermeture de Modrinth App."
},
"app.update-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."
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+112 -28
View File
@@ -5,6 +5,39 @@
"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": "הזן את תיאור חבילת המודים..."
},
@@ -41,18 +74,21 @@
"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}\" נוסף"
},
@@ -65,17 +101,53 @@
"app.instance.mods.share-title": {
"message": "משתף את התוכן של חבילת המודים"
},
"app.instance.mods.show-file": {
"message": "מצא קובץ"
},
"app.instance.mods.successfully-uploaded": {
"message": "הועלה בהצלחה"
},
"app.instance.mods.unknown-version": {
"message": "לא ידוע"
"app.instance.worlds.add-server": {
"message": "הוסף שרת"
},
"app.instance.mods.updating": {
"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": "תוכן נדרש"
@@ -125,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "אפשרויות ברירת מחדל של התקנה"
},
"app.settings.tabs.feature-flags": {
"message": "הגדרות ניסיוניות"
},
"app.settings.tabs.java-installations": {
"message": "התקנות ג'אווה"
},
@@ -182,6 +251,24 @@
"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": "הוספת חבר"
},
@@ -281,6 +368,12 @@
"instance.edit-world.title": {
"message": "ערוך עולם"
},
"instance.files.adding-files": {
"message": "הוספת קבצים ({completed}/{total})"
},
"instance.files.save-as": {
"message": "שמור כ..."
},
"instance.server-modal.address": {
"message": "כתובת"
},
@@ -452,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "אל תציג בדף הבית"
},
"instance.worlds.filter.available": {
"message": "זמין"
},
"instance.worlds.game_already_open": {
"message": "התקנה כבר פתוחה"
},
@@ -479,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "שחק בהתקנה"
},
"instance.worlds.type.server": {
"message": "שרת"
},
"instance.worlds.type.singleplayer": {
"message": "שחקן יחיד"
},
"instance.worlds.view_instance": {
"message": "הצג התקנה"
},
+113 -44
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Profil törlése"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Ez a modcsomag már telepítve van a{instanceName} profilban."
"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-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.create": {
"message": "Létrehozás"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Ismeretlen"
"app.instance.worlds.add-server": {
"message": "Szerver hozzáadása"
},
"app.instance.mods.updating": {
"message": "Frissítés..."
"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.modal.install-to-play.content-required": {
"message": "Szükséges tartalom"
@@ -102,7 +159,7 @@
"message": "Telepítés"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural,one {# mod}other {# modok}}"
"message": "{count} mod"
},
"app.modal.install-to-play.required-modpack": {
"message": "Szükséges modcsomag"
@@ -140,9 +197,6 @@
"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"
},
@@ -174,7 +228,7 @@
"message": "Letöltés ({size})"
},
"app.update-popup.download-complete": {
"message": "Letöltés sikeres"
"message": "Sikeres letöltés"
},
"app.update-popup.reload": {
"message": "Újratöltés"
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -477,13 +552,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": "Nem lehet kapcsolatot létesíteni a szerverrel"
"message": "A szerverrel nem lehet kapcsolatot létesíteni"
},
"instance.worlds.no_server_quick_play": {
"message": "Csak Minecraft Alpha 1.0.5+-tól tudsz egyből szerverhez csatlakozni"
@@ -494,12 +569,6 @@
"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"
},
@@ -513,13 +582,13 @@
"message": "A játékverzió a profil által van megadva"
},
"search.filter.locked.instance-loader.title": {
"message": "A modbetöltő a profil által van megadva"
"message": "A betöltő a profil által van megadva"
},
"search.filter.locked.instance.sync": {
"message": "Profil szinkronizálása"
},
"search.filter.locked.server": {
"message": "A szerver biztosítja"
"message": "A szerver által van megadva"
},
"search.filter.locked.server-environment.title": {
"message": "Csak kliensoldali modok adhatók hozzá a szerverprofilhoz"
+107 -38
View File
@@ -5,6 +5,39 @@
"app.auth-servers.unreachable.header": {
"message": "Tidak dapat terhubung ke server autentikasi"
},
"app.browse.add-server-to-instance": {
"message": "Tambah server ke instans"
},
"app.browse.add-servers-to-instance": {
"message": "Tambah server ke instans Anda"
},
"app.browse.add-to-instance": {
"message": "Tambah ke instans"
},
"app.browse.add-to-instance-name": {
"message": "Tambah ke {instanceName}"
},
"app.browse.added": {
"message": "Ditambahkan"
},
"app.browse.already-added": {
"message": "Telah ditambahkan"
},
"app.browse.discover-content": {
"message": "Temukan konten"
},
"app.browse.discover-servers": {
"message": "Temukan server"
},
"app.browse.hide-added-servers": {
"message": "Sembuyikan server tertambah"
},
"app.browse.hide-installed-content": {
"message": "Sembunyikan konten terpasang"
},
"app.browse.install-content-to-instance": {
"message": "Pasang konten ke instans"
},
"app.export-modal.description-placeholder": {
"message": "Masukkan deskripsi paket mod..."
},
@@ -41,33 +74,21 @@
"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.body": {
"message": "Paket mod ini telah terpasang di instans <bold>{instanceName}</bold>. Apakah Anda yakin ingin menggandakannya?"
},
"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.create": {
"message": "Buat"
},
"app.instance.modpack-already-installed.header": {
"message": "Paket mod telah terpasang"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instans"
},
"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"
},
@@ -80,17 +101,53 @@
"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.worlds.add-server": {
"message": "Tambah server"
},
"app.instance.mods.updating": {
"message": "Memperbarui..."
"app.instance.worlds.browse-servers": {
"message": "Telusuri server"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' akan **dihapus secara permanen**, dan Anda tidak akan dapat memulihkannya."
},
"app.instance.worlds.delete-world-title": {
"message": "Apakah Anda yakin ingin menghapus dunia ini secara permanen?"
},
"app.instance.worlds.filter-modded": {
"message": "Dimodifikasi"
},
"app.instance.worlds.filter-offline": {
"message": "Luring"
},
"app.instance.worlds.filter-online": {
"message": "Daring"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanila"
},
"app.instance.worlds.no-worlds-description": {
"message": "Tambah server atau telusuri untuk memulai"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Tidak ada server atau dunia yang ditambahkan"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' akan dihapus dari daftar Anda, termasuk di dalam permainan, dan Anda tidak akan dapat memulihkannya."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) akan dihapus dari daftar Anda, termasuk di dalam permainan, dan Anda tidak akan dapat memulihkannya."
},
"app.instance.worlds.remove-server-title": {
"message": "Apakah Anda yakin ingin menghapus {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cari {count} dunia..."
},
"app.instance.worlds.this-server": {
"message": "server ini"
},
"app.modal.install-to-play.content-required": {
"message": "Konten diperlukan"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"app.update.reload-to-update": {
"message": "Muat ulang untuk memasang pembaruan"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
"app.world.server-modal.select-an-option": {
"message": "Pilih opsi"
},
"app.world.world-item.incompatible-version": {
"message": "Versi {version} tidak cocok"
},
"app.world.world-item.not-played-yet": {
"message": "Belum pernah dimainkan"
},
"app.world.world-item.offline": {
"message": "Luring"
},
"app.world.world-item.players-online": {
"message": "{count} orang daring"
},
"friends.action.add-friend": {
"message": "Tambah teman"
},
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "Sunting dunia"
},
"instance.files.adding-files": {
"message": "Menambahkan berkas ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Simpan sebagai..."
},
"instance.server-modal.address": {
"message": "Alamat"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+108 -39
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Elimina istanza"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Questo pacchetto è già stato installato nell'istanza \"{instanceName}\"."
"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-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.create": {
"message": "Crea"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Sconosciuta"
"app.instance.worlds.add-server": {
"message": "Aggiungi server"
},
"app.instance.mods.updating": {
"message": "Aggiornando..."
"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.modal.install-to-play.content-required": {
"message": "Contenuto richiesto"
@@ -140,9 +197,6 @@
"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"
},
@@ -192,11 +246,29 @@
"message": "Scarica aggiornamento"
},
"app.update.downloading-update": {
"message": "Scaricando aggiornamento ({percent}%)"
"message": "Scaricando l'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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+109 -40
View File
@@ -5,6 +5,39 @@
"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パックの説明を入力…"
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "インスタンスを削除"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "このModパックは「{instanceName}インスタンスにすでにインストールされています。"
"app.instance.modpack-already-installed.body": {
"message": "このModpackは既に <bold>{instanceName}</bold> インスタンスにインストール済みです。本当に複製しますか?"
},
"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.create": {
"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}」が追加されました"
},
@@ -80,17 +101,53 @@
"app.instance.mods.share-title": {
"message": "Modパックのコンテンツを共有"
},
"app.instance.mods.show-file": {
"message": "ファイルを表示"
},
"app.instance.mods.successfully-uploaded": {
"message": "アップロードに成功しました"
},
"app.instance.mods.unknown-version": {
"message": "不明"
"app.instance.worlds.add-server": {
"message": "サーバーを追加"
},
"app.instance.mods.updating": {
"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.modal.install-to-play.content-required": {
"message": "必須コンテンツ"
@@ -140,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "インスタンスの基本設定"
},
"app.settings.tabs.feature-flags": {
"message": "機能設定"
},
"app.settings.tabs.java-installations": {
"message": "Javaのインストール設定"
},
@@ -197,6 +251,24 @@
"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": "フレンドを追加"
},
@@ -210,10 +282,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ユーザー名は何ですか?"
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "ワールドを編集"
},
"instance.files.adding-files": {
"message": "ファイルを追加中 ({completed}/{total})"
},
"instance.files.save-as": {
"message": "名前をつけて保存…"
},
"instance.server-modal.address": {
"message": "アドレス"
},
@@ -467,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "ホームに表示しない"
},
"instance.worlds.filter.available": {
"message": "利用可能"
},
"instance.worlds.game_already_open": {
"message": "インスタンスは既に開いています"
},
@@ -494,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "インスタンスを起動"
},
"instance.worlds.type.server": {
"message": "サーバー"
},
"instance.worlds.type.singleplayer": {
"message": "シングルプレイ"
},
"instance.worlds.view_instance": {
"message": "インスタンスを表示する"
},
+171 -18
View File
@@ -5,8 +5,149 @@
"app.auth-servers.unreachable.header": {
"message": "인증 서버에 연결할 수 없습니다"
},
"app.instance.mods.updating": {
"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.modal.install-to-play.content-required": {
"message": "콘텐츠 설치 필요"
@@ -56,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "기본 인스턴스 옵션"
},
"app.settings.tabs.feature-flags": {
"message": "기능 플래그"
},
"app.settings.tabs.java-installations": {
"message": "Java 설치"
},
@@ -78,7 +216,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}을 지금 사용할 수 있습니다! 데이터 통신 연결을 사용 중이므로, 자동으로 다운로드하지 않았습니다."
@@ -87,7 +225,7 @@
"message": "변경 내역"
},
"app.update-popup.download": {
"message": "({size}) 다운로드"
"message": "다운로드 ({size})"
},
"app.update-popup.download-complete": {
"message": "다운로드 완료"
@@ -113,6 +251,24 @@
"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": "친구 추가"
},
@@ -165,7 +321,7 @@
"message": "대기 중"
},
"friends.no-friends-match": {
"message": "\"{query}\" 이름과 일치하는 친구 없음"
"message": "\"{query}\"과(와) 일치하는 친구 없음"
},
"friends.search-friends-placeholder": {
"message": "친구 검색 중..."
@@ -212,6 +368,12 @@
"instance.edit-world.title": {
"message": "세계 편집"
},
"instance.files.adding-files": {
"message": "파일 추가 중 ({completed}/{total})"
},
"instance.files.save-as": {
"message": "다른 이름으로 저장..."
},
"instance.server-modal.address": {
"message": "주소"
},
@@ -258,7 +420,7 @@
"message": "아이콘 제거"
},
"instance.settings.tabs.general.edit-icon.replace": {
"message": "아이콘 체"
"message": "아이콘 체"
},
"instance.settings.tabs.general.edit-icon.select": {
"message": "아이콘 선택"
@@ -383,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "홈에서 숨기기"
},
"instance.worlds.filter.available": {
"message": "이용 가능"
},
"instance.worlds.game_already_open": {
"message": "이미 실행된 인스턴스"
},
@@ -410,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "인스턴스 플레이"
},
"instance.worlds.type.server": {
"message": "서버"
},
"instance.worlds.type.singleplayer": {
"message": "싱글 플레이"
},
"instance.worlds.view_instance": {
"message": "인스턴스 보기"
},
+107 -32
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,27 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Padam pemasangan"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Pek mod ini sudah pun dipasang di dalam pemasangan \"{instanceName}\"."
"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.go-to-instance": {
"message": "Pergi ke pemasangan"
"app.instance.modpack-already-installed.create": {
"message": "Cipta"
},
"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"
},
@@ -74,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Tidak Diketahui"
"app.instance.worlds.add-server": {
"message": "Tambah pelayan"
},
"app.instance.mods.updating": {
"message": "Sedang mengemas kini..."
"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.modal.install-to-play.content-required": {
"message": "Kandungan yang diperlukan"
@@ -134,9 +197,6 @@
"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"
},
@@ -191,6 +251,24 @@
"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"
},
@@ -290,6 +368,12 @@
"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"
},
@@ -461,9 +545,6 @@
"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"
},
@@ -488,12 +569,6 @@
"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"
},
+111 -42
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,38 +74,26 @@
"app.instance.confirm-delete.header": {
"message": "Verwijder instantie"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Deze modpack is al geïnstalleerd in de \"{instanceName}\" 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 het geen wil dupliceren?"
},
"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.create": {
"message": "Maak"
},
"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"
},
"app.instance.mods.projects-were-added": {
"message": "{count} projecten waren toegevoegd"
"message": "{count} Projecten waren toegevoegd"
},
"app.instance.mods.share-text": {
"message": "Bekijk de projecten die ik in mijn modpack gebruik!"
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Onbekend"
"app.instance.worlds.add-server": {
"message": "Voeg server toe"
},
"app.instance.mods.updating": {
"message": "Aan het updaten..."
"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.delete-world-title": {
"message": "Weet je zeker dat je deze wereld definitief wilt verwijderen?"
},
"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-description": {
"message": "'{name}' wordt uit je lijst verwijderd, ook in de game, en kan op geen enkele manier worden hersteld."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) wordt uit je lijst verwijderd, ook in de game, en kan op geen enkele manier worden hersteld."
},
"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.modal.install-to-play.content-required": {
"message": "Content vereist"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -378,7 +456,7 @@
"message": "Uitgevoerd nadat het spel is afgesloten."
},
"instance.settings.tabs.hooks.post-exit.enter": {
"message": "Voer na-afsluitcommando in..."
"message": "Voer een opdracht na het afsluiten in..."
},
"instance.settings.tabs.hooks.pre-launch": {
"message": "Voor het opstarten"
@@ -444,7 +522,7 @@
"message": "De hoogte van het spelvenster wanneer opgestart."
},
"instance.settings.tabs.window.height.enter": {
"message": "Vul hoogte in..."
"message": "Voer de lengte in..."
},
"instance.settings.tabs.window.width": {
"message": "Breedte"
@@ -467,9 +545,6 @@
"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"
},
@@ -489,17 +564,11 @@
"message": "Je kan alleen direct de servers in springen in Minecraft Alpha 1.0.5+"
},
"instance.worlds.no_singleplayer_quick_play": {
"message": "Je kan alleen direct je eigen werelden in springen in Minecraft 1.20+"
"message": "Je kan alleen direct je eigen werelden in springen in Minecraft 1,20+"
},
"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,9 +41,6 @@
"app.settings.tabs.default-instance-options": {
"message": "Standard instansinstillinger"
},
"app.settings.tabs.feature-flags": {
"message": "Funksjonsflagg"
},
"app.settings.tabs.java-installations": {
"message": "Java installasjoner"
},
@@ -341,9 +338,6 @@
"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"
},
@@ -365,12 +359,6 @@
"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"
},
+117 -33
View File
@@ -5,6 +5,42 @@
"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"
},
@@ -38,45 +74,81 @@
"app.instance.confirm-delete.header": {
"message": "Usuń instancję"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Ta paczka modów jest już zainstalowana w instancji \"{instanceName}\"."
"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-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.create": {
"message": "Utwórz"
},
"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.show-file": {
"message": "Pokaż plik"
"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.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"
},
@@ -125,9 +197,6 @@
"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"
},
@@ -182,6 +251,24 @@
"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"
},
@@ -281,6 +368,12 @@
"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"
},
@@ -452,9 +545,6 @@
"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"
},
@@ -479,12 +569,6 @@
"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ę"
},
+109 -40
View File
@@ -1,12 +1,45 @@
{
"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 internet 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 rede 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..."
"message": "Insira uma descrição para o pacote de mods..."
},
"app.export-modal.export-button": {
"message": "Exportar"
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Excluir instância"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Este pacote de mods já está instalado na instância \"{instanceName}\"."
"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-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.create": {
"message": "Criar"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Desconhecido"
"app.instance.worlds.add-server": {
"message": "Adicionar servidor"
},
"app.instance.mods.updating": {
"message": "Atualizando..."
"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.modal.install-to-play.content-required": {
"message": "Conteúdo necessário"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+222 -12
View File
@@ -5,6 +5,153 @@
"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"
},
@@ -14,12 +161,21 @@
"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"
},
@@ -41,9 +197,6 @@
"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"
},
@@ -56,6 +209,33 @@
"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."
},
@@ -71,6 +251,24 @@
"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"
},
@@ -170,6 +368,12 @@
"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"
},
@@ -341,9 +545,6 @@
"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"
},
@@ -353,6 +554,9 @@
"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"
},
@@ -365,12 +569,6 @@
"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"
},
@@ -388,5 +586,17 @@
},
"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"
}
}
+3 -15
View File
@@ -5,9 +5,6 @@
"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"
},
@@ -56,9 +53,6 @@
"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"
},
@@ -113,6 +107,9 @@
"app.update.reload-to-update": {
"message": "Reîncarcă pentru a instala actualizarea"
},
"app.world.server-modal.select-an-option": {
"message": "Selectează o opțiune"
},
"friends.action.add-friend": {
"message": "Adaugă un prieten"
},
@@ -383,9 +380,6 @@
"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ă"
},
@@ -410,12 +404,6 @@
"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"
},
+120 -51
View File
@@ -5,6 +5,39 @@
"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": "Введите описание сборки..."
},
@@ -12,7 +45,7 @@
"message": "Экспортировать"
},
"app.export-modal.header": {
"message": "Экспорт модпака"
"message": "Экспорт сборки"
},
"app.export-modal.modpack-name-label": {
"message": "Название сборки"
@@ -21,7 +54,7 @@
"message": "Название сборки"
},
"app.export-modal.select-files-label": {
"message": "Выберите элементы экспорта в модпак"
"message": "Выберите файлы и папки для экспорта"
},
"app.export-modal.version-number-label": {
"message": "Номер версии"
@@ -30,10 +63,10 @@
"message": "1.0.0"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Все данные вашей инстанции будут окончательно удалены, включая ваши миры, настройки и весь установленный контент."
"message": "Все данные сборки будут удалены навсегда, в том числе миры, настройки и весь установленный контент."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Это действие нельзя обратить"
"message": "Это действие необратимо"
},
"app.instance.confirm-delete.delete-button": {
"message": "Удалить сборку"
@@ -41,56 +74,80 @@
"app.instance.confirm-delete.header": {
"message": "Удаление сборки"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "Этот модпак уже установлен в инстанции \"{instanceName}\"."
"app.instance.modpack-already-installed.body": {
"message": "Выбранное содержимое уже установлено в сборке <bold>{instanceName}</bold>. Создать копию?"
},
"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.create": {
"message": "Создать"
},
"app.instance.modpack-already-installed.header": {
"message": "Модпак уже установлен"
"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}\" был добавлен"
},
"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.mods.unknown-version": {
"message": "Неизвестно"
"app.instance.worlds.add-server": {
"message": "Добавить сервер"
},
"app.instance.mods.updating": {
"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.modal.install-to-play.content-required": {
"message": "Требуется дополнительный контент"
@@ -117,7 +174,7 @@
"message": "Общая сборка сервера"
},
"app.modal.install-to-play.view-contents": {
"message": "Посмотреть"
"message": "Посмотреть содержимое"
},
"app.modal.update-to-play.header": {
"message": "Обновление перед запуском"
@@ -140,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "Предустановки"
},
"app.settings.tabs.feature-flags": {
"message": "Флаги функций"
},
"app.settings.tabs.java-installations": {
"message": "Установки Java"
},
@@ -197,6 +251,24 @@
"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": "Добавить в друзья"
},
@@ -219,7 +291,7 @@
"message": "Какое имя у друга на Modrinth?"
},
"friends.add-friends-to-share": {
"message": "<link>Добавьте друзей</link>, чтобы видеть, во что они играют!"
"message": "<link>Добавьте друзей</link> и следите за ними!"
},
"friends.friend.cancel-request": {
"message": "Отменить запрос"
@@ -258,7 +330,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": "Добавить и играть"
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "Настройка мира"
},
"instance.files.adding-files": {
"message": "Добавление файлов ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Сохранить как..."
},
"instance.server-modal.address": {
"message": "Адрес"
},
@@ -318,7 +396,7 @@
"message": "Удалить сборку"
},
"instance.settings.tabs.general.delete.description": {
"message": "Навсегда удаляет сборку с вашего устройства, включая миры, настройки и весь установленный контент. Учтите, что после удаления сборки восстановить её будет невозможно."
"message": "Навсегда удаляет сборку с устройства, в том числе миры, настройки и весь установленный контент. Учтите, что после удаления сборки её невозможно восстановить."
},
"instance.settings.tabs.general.deleting.button": {
"message": "Удаление..."
@@ -354,7 +432,7 @@
"message": "Создать новую группу"
},
"instance.settings.tabs.general.library-groups.description": {
"message": "Разделение по группам позволяет организовать сборки по разным разделам в библиотеке."
"message": "Позволяет организовать сборки по разным разделам в библиотеке."
},
"instance.settings.tabs.general.library-groups.enter-name": {
"message": "Введите название группы"
@@ -467,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "Не показывать на главной"
},
"instance.worlds.filter.available": {
"message": "Доступно"
},
"instance.worlds.game_already_open": {
"message": "Сборка уже запущена"
},
@@ -494,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "Запустить сборку"
},
"instance.worlds.type.server": {
"message": "Сервер"
},
"instance.worlds.type.singleplayer": {
"message": "Одиночная игра"
},
"instance.worlds.view_instance": {
"message": "Посмотреть сборку"
},
@@ -522,7 +591,7 @@
"message": "Управляется сервером"
},
"search.filter.locked.server-environment.title": {
"message": "В серверную сборку можно добавлять только моды для клиента"
"message": "В серверной сборке доступны только клиентские моды"
},
"search.filter.locked.server-game-version.title": {
"message": "Версия игры управляется сервером"
+133 -10
View File
@@ -1,30 +1,153 @@
{
"app.auth-servers.unreachable.body": {
"message": "Minecraft serveri za autentifikaciju su možda trenutno nedostupni. Molimo vas da proverite vašu internet vezu i pokušajte ponovo kasnije."
},
"app.auth-servers.unreachable.header": {
"message": "Serveri za autentifikaciju su nedostupni"
},
"app.browse.add-server-to-instance": {
"message": "Dodaj server na instancu"
},
"app.browse.add-servers-to-instance": {
"message": "Dodaj servere na tvoju instancu"
},
"app.browse.add-to-instance": {
"message": "Dodaj na instancu"
},
"app.browse.add-to-instance-name": {
"message": "Dodaj na {instanceName}"
},
"app.browse.added": {
"message": "Dodato"
},
"app.browse.already-added": {
"message": "Već je dodato"
},
"app.browse.discover-content": {
"message": "Otkrij sadržaj"
},
"app.browse.discover-servers": {
"message": "Otkrij servere"
},
"app.browse.hide-added-servers": {
"message": "Sakrije dodate servere"
},
"app.browse.hide-installed-content": {
"message": "Sakrije instalirani sadržaj"
},
"app.browse.install-content-to-instance": {
"message": "Instaliraj sadržaj na instancu"
},
"app.export-modal.description-placeholder": {
"message": "Ukucaj opis modpacka..."
},
"app.export-modal.export-button": {
"message": "Izvoz"
},
"app.export-modal.header": {
"message": "Izvezi modpack"
},
"app.export-modal.modpack-name-label": {
"message": "Ime Modpacka"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Ime modpacka"
},
"app.export-modal.select-files-label": {
"message": "Izaberi fajlove i foldere da uključiš u pack"
},
"app.export-modal.version-number-label": {
"message": "Broj verzije"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.instance.mods.copy-link": {
"message": "Kopiraj link"
"app.instance.confirm-delete.admonition-body": {
"message": "Svi podaci za tvoju instancu će biti trajno izbrisani, uključujući tvoje svetove, konfiguracije, i sav instaliran sadržaj."
},
"app.instance.mods.installing": {
"message": "Instaliranje..."
"app.instance.confirm-delete.admonition-header": {
"message": "Ova akcija ne može biti poništena"
},
"app.instance.mods.show-file": {
"message": "Prikaži datoteku"
"app.instance.confirm-delete.delete-button": {
"message": "Izbriši instancu"
},
"app.instance.confirm-delete.header": {
"message": "Izbriši instancu"
},
"app.instance.modpack-already-installed.body": {
"message": "Ovaj modpack je već instaliran u <bold>{instanceName}</bold> instancu. Da li ste sigurni da želite da je duplirate?"
},
"app.instance.modpack-already-installed.create": {
"message": "Napravi"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpack je već instaliran"
},
"app.instance.modpack-already-installed.instance": {
"message": "Instanca"
},
"app.instance.mods.content-type-project": {
"message": "projekat"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" je dodato"
},
"app.instance.mods.projects-were-added": {
"message": "{count} projekata je dodato"
},
"app.instance.mods.share-text": {
"message": "Pogledaj ove projekte koje ja koristim u svom modpacku!"
},
"app.instance.mods.share-title": {
"message": "Deljenje sadržaja modpacka"
},
"app.instance.mods.successfully-uploaded": {
"message": "Uspešno otpremljeno"
},
"app.instance.mods.unknown-version": {
"message": "Nepoznato"
"app.instance.worlds.add-server": {
"message": "Dodaj server"
},
"app.instance.mods.updating": {
"message": "Ažuriranje..."
"app.instance.worlds.browse-servers": {
"message": "Pretraži servere"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' će biti **trajno izbrisan**, i neće biti načina da se vrati."
},
"app.instance.worlds.delete-world-title": {
"message": "Da li ste sigurni da hoćete da trajno izbrišete ovaj svet?"
},
"app.instance.worlds.filter-modded": {
"message": "Modifikovano"
},
"app.instance.worlds.filter-offline": {
"message": "Oflajn"
},
"app.instance.worlds.filter-online": {
"message": "Onlajn"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanila"
},
"app.instance.worlds.no-worlds-description": {
"message": "Dodaj server ili pretraži da bi započeo"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nema dodatih servera niti svetova"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' će biti izbrisan sa vaše liste, uključujući i unutar igre, i neće biti načina da se oporavi."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) će biti izbrisan sa vaše liste, uključujući i unutar igre, i neće biti načina da se oporavi."
},
"app.instance.worlds.remove-server-title": {
"message": "Da li ste sigurni da hoćete da izbrišete {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Pretraži {count} svetova..."
},
"app.instance.worlds.this-server": {
"message": "ovaj server"
},
"app.settings.tabs.appearance": {
"message": "Izgled"
+112 -31
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,21 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Radera instans"
},
"app.instance.modpack-already-installed.create-anyway": {
"message": "Skapa ändå"
"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.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"
},
@@ -68,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Okänt"
"app.instance.worlds.add-server": {
"message": "Lägg till server"
},
"app.instance.mods.updating": {
"message": "Uppdaterar..."
"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.modal.install-to-play.content-required": {
"message": "Innehåll krävs"
@@ -128,9 +197,6 @@
"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"
},
@@ -165,7 +231,7 @@
"message": "Nedladdning slutförd"
},
"app.update-popup.reload": {
"message": "Kolla efter uppdateringar"
"message": "Ladda om"
},
"app.update-popup.title": {
"message": "Uppdatering tillgänglig"
@@ -185,6 +251,24 @@
"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"
},
@@ -284,6 +368,12 @@
"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"
},
@@ -455,9 +545,6 @@
"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"
},
@@ -482,12 +569,6 @@
"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"
},
+316 -7
View File
@@ -1,34 +1,178 @@
{
"app.auth-servers.unreachable.body": {
"message": "เซิร์ฟเวอร์ตรวจสอบสิทธิ์ของ Minecraft อาจใช้งานไม่ได้ในขณะนี้ โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณและลองใหม่อีกครั้งในภายหลัง"
},
"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.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.modpack-name-label": {
"message": "ชื่อมอดแพ็ก"
},
"app.export-modal.modpack-name-placeholder": {
"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.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.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.install-to-play.view-contents": {
"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 ที่ติดตั้ง"
},
@@ -41,6 +185,27 @@
"app.settings.tabs.resource-management": {
"message": "การจัดการทรัพยากร"
},
"app.update-popup.body": {
"message": "แอฟ Modrinth v{version} พร้อมสำหรับการติดตั้งแล้ว! รีโหลดแอปเพื่ออัปเดต หรือ อัปเดตอัตโนมัติเมื่อคุณปิดแอฟ Modrinth"
},
"app.update-popup.body.download-complete": {
"message": "แอฟ Modrinth v{version} ดาวน์โหลดเสร็จแล้ว รีโหลดแอปเพื่ออัปเดต หรือ อัปเดตอัตโนมัติเมื่อคุณปิดแอฟ Modrinth"
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} พร้อมให้อัปเดตแล้ว โปรดใช้ตัวจัดการแพ็กเกจ ของคุณเพื่ออัปเดตฟีเจอร์ใหม่และตัวแก้ไขล่าสุด!"
},
"app.update-popup.body.metered": {
"message": "แอฟ Modrinth v{version} พร้อมแล้ว! เนื่องจากคุณเปิดโหมดจำกัดปริมาณข้อมูล เราจึงไม่ได้อัปเดตอัตโนมัติ"
},
"app.update-popup.download": {
"message": "ดาวโหลด ({size})"
},
"app.update-popup.download-complete": {
"message": "ดาวน์โหลดเสร็จสมบูรณ์"
},
"app.update-popup.title": {
"message": "มีการอัปเดตใหม่"
},
"app.update.complete-toast.text": {
"message": "คลิกที่นี่เพื่อดูบันทึกการเปลี่ยนแปลง"
},
@@ -56,6 +221,21 @@
"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.offline": {
"message": "ออฟไลน์"
},
"app.world.world-item.players-online": {
"message": "{count} ออนไลน์"
},
"friends.action.add-friend": {
"message": "เพิ่มเพื่อน"
},
@@ -101,6 +281,9 @@
"friends.heading.online": {
"message": "ออฟไลน์"
},
"friends.heading.pending": {
"message": "กําลังดำเนินการ"
},
"friends.no-friends-match": {
"message": "ไม่มีเพื่อนชื่อว่า \"{query}\""
},
@@ -149,6 +332,12 @@
"instance.edit-world.title": {
"message": "แก้ไขโลก"
},
"instance.files.adding-files": {
"message": "กำลังเพิ่มไฟล์ ({completed}/{total})"
},
"instance.files.save-as": {
"message": "บันทึกเป็น..."
},
"instance.server-modal.address": {
"message": "ที่อยู่เซิร์ฟเวอร์"
},
@@ -215,13 +404,133 @@
"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.instance.sync": {
"message": "ซิงค์กับอินสแตนซ์"
},
"search.filter.locked.server": {
"message": "เซิร์ฟเวอร์เป็นผู้จัดเตรียม"
},
"search.filter.locked.server-game-version.title": {
"message": "เซิร์ฟเวอร์เป็นผู้จัดเตรียมเวอร์ชันเกม"
}
}
+137 -44
View File
@@ -5,48 +5,90 @@
"app.auth-servers.unreachable.header": {
"message": "Doğrulama sunucularına erişilemedi"
},
"app.browse.add-server-to-instance": {
"message": "Sunucuyu kuruluma ekle"
},
"app.browse.add-servers-to-instance": {
"message": "Kurulumunuza sunucular ekleyin"
},
"app.browse.add-to-instance": {
"message": "Kuruluma ekle"
},
"app.browse.add-to-instance-name": {
"message": "{instanceName}'a ekle"
},
"app.browse.added": {
"message": "Ekli"
},
"app.browse.already-added": {
"message": "Zaten ekli"
},
"app.browse.discover-content": {
"message": "İçerik keşfet"
},
"app.browse.discover-servers": {
"message": "Sunucu keşfet"
},
"app.browse.hide-added-servers": {
"message": "Ekli sunucuları gizle"
},
"app.browse.hide-installed-content": {
"message": "Kurulu içerikleri gizle"
},
"app.browse.install-content-to-instance": {
"message": "İçeriği kuruluma yükle"
},
"app.export-modal.description-placeholder": {
"message": "Mod paketi açıklaması yazın..."
"message": "Mod paketi açıklaması girin..."
},
"app.export-modal.export-button": {
"message": "Çıkart"
"message": "Dışa aktar"
},
"app.export-modal.header": {
"message": "Modpaketi çıkart"
"message": "Mod paketini dışa aktar"
},
"app.export-modal.modpack-name-label": {
"message": "Modpaketi adı"
"message": "Mod Paketi 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.modpack-already-installed.create-anyway": {
"message": "Yinede oluştur"
"app.instance.confirm-delete.delete-button": {
"message": "Kurulumu sil"
},
"app.instance.confirm-delete.header": {
"message": "Kurulumu sil"
},
"app.instance.modpack-already-installed.body": {
"message": "Bu mod paketi zaten <bold>{instanceName}</bold> kurulumunda yüklü. Kopyalamak istediğinizden emin misiniz?"
},
"app.instance.modpack-already-installed.create": {
"message": "Oluştur"
},
"app.instance.modpack-already-installed.header": {
"message": "Modpaketi zaten kurulu"
"message": "Modpaketi zaten yüklü"
},
"app.instance.modpack-already-installed.instance": {
"message": "Kurulum"
},
"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"
},
@@ -56,23 +98,62 @@
"app.instance.mods.share-text": {
"message": "Modpaketimde kullandığım projelere göz at!"
},
"app.instance.mods.show-file": {
"message": "Dosya göster"
"app.instance.mods.share-title": {
"message": "Mod paketi içeriğini paylaşıyorsunuz"
},
"app.instance.mods.successfully-uploaded": {
"message": "Başarı ile yüklendi"
"message": "Başarıyla yüklendi"
},
"app.instance.mods.unknown-version": {
"message": "Bilinmeyen"
"app.instance.worlds.add-server": {
"message": "Sunucu Ekle"
},
"app.instance.mods.updating": {
"message": "Güncelleniyor..."
"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": "Çevrim dışı"
},
"app.instance.worlds.filter-online": {
"message": "Çevrim iç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}'i 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.modal.install-to-play.content-required": {
"message": "İçerik gerekli"
},
"app.modal.install-to-play.header": {
"message": "Yükleyip oynayın"
"message": "Oynamak için yükleyin"
},
"app.modal.install-to-play.install-button": {
"message": "Yükle"
@@ -116,9 +197,6 @@
"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ı"
},
@@ -132,19 +210,19 @@
"message": "Kaynak yönetimi"
},
"app.update-popup.body": {
"message": "Modrinth App v{version} güncellemesi hazır! Hemen güncellemek için yeniden yükle veya Modrinth Appi kapattığında otomatik olarak güncellenecek."
"message": "Modrinth App v{version} güncellemesi indirilmeye hazır! Hemen güncellemek için yeniden başlatın veya Modrinth Appi kapattığınızda otomatik olarak güncellenecek."
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} indirildi. Güncellemek için sayfayı yeniden yükle veya Modrinth Appi kapattığında otomatik olarak güncellenecek."
"message": "Modrinth App v{version} indirildi. Güncellemek için yeniden başlatın veya Modrinth Appi kapattığınızda otomatik olarak güncellenecek."
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} yayımlandı. En yeni özellikler ve hata düzeltmeleri için paket yöneticin üzerinden güncelle!"
},
"app.update-popup.body.metered": {
"message": "Modrinth App v{version} artık mevcut! Ölçüllü bağlantıda olduğunuzdan otomatik olarak indirmedik."
"message": "Modrinth App v{version} mevcut! Kısıtlı bağlantıda olduğunuzdan otomatik olarak indirmedik."
},
"app.update-popup.changelog": {
"message": "Değişiklikler"
"message": "Yama Notları"
},
"app.update-popup.download": {
"message": "İndir ({size})"
@@ -153,7 +231,7 @@
"message": "İndirme tamamlandı"
},
"app.update-popup.reload": {
"message": "Yeniden yükle"
"message": "Yeniden başlat"
},
"app.update-popup.title": {
"message": "Güncelleme mevcut"
@@ -171,7 +249,25 @@
"message": "Güncelleme indiriliyor (%{percent})"
},
"app.update.reload-to-update": {
"message": "Güncellemeyi kurmak için yeniden yükleyin"
"message": "Güncellemeyi kurmak için yeniden başlatın"
},
"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": "Çevrim dışı"
},
"app.world.world-item.players-online": {
"message": "{count} çevrimiçi"
},
"friends.action.add-friend": {
"message": "Bir arkadaş ekle"
@@ -272,6 +368,12 @@
"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"
},
@@ -288,10 +390,10 @@
"message": "Genel"
},
"instance.settings.tabs.general.delete": {
"message": "Profili sil"
"message": "Kurulumu sil"
},
"instance.settings.tabs.general.delete.button": {
"message": "Profili sil"
"message": "Kurulumu sil"
},
"instance.settings.tabs.general.delete.description": {
"message": "Profili, içindeki dünyaları, ayarları ve indirilen şeyleri cihazınızdan sonsuza kadar siler. Dikkatli olun, bir profil silindikten sonra geri alınamaz."
@@ -443,9 +545,6 @@
"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"
},
@@ -470,12 +569,6 @@
"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"
},
+107 -38
View File
@@ -5,6 +5,39 @@
"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": "Уведіть опис збірки…"
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Видалити профіль"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": ю збірку вже встановлено в профілі «{instanceName}»."
"app.instance.modpack-already-installed.body": {
"message": я збірка вже встановлена в профілі <bold>«{instanceName}»</bold>. Ви впевнені, що хочете її скопіювати?"
},
"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.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}» було додано"
},
@@ -80,17 +101,53 @@
"app.instance.mods.share-title": {
"message": "Поширення вмісту збірки"
},
"app.instance.mods.show-file": {
"message": "Показати файл"
},
"app.instance.mods.successfully-uploaded": {
"message": "Успішно вивантажено"
},
"app.instance.mods.unknown-version": {
"message": "Невідомо"
"app.instance.worlds.add-server": {
"message": "Додати сервер"
},
"app.instance.mods.updating": {
"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": "Потрібний уміст"
@@ -140,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "Усталені налашт. профілю"
},
"app.settings.tabs.feature-flags": {
"message": "Спеціальні функції"
},
"app.settings.tabs.java-installations": {
"message": "Інсталяції Java"
},
@@ -197,6 +251,24 @@
"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": "Додати друга"
},
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "Редагувати світ"
},
"instance.files.adding-files": {
"message": "Додавання файлів ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Зберегти як…"
},
"instance.server-modal.address": {
"message": "Адреса"
},
@@ -467,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "Не показувати на головній"
},
"instance.worlds.filter.available": {
"message": "Доступно"
},
"instance.worlds.game_already_open": {
"message": "Профіль вже запущений"
},
@@ -494,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "Грати у профіль"
},
"instance.worlds.type.server": {
"message": "Сервер"
},
"instance.worlds.type.singleplayer": {
"message": "Гра наодинці"
},
"instance.worlds.view_instance": {
"message": "Переглянути профіль"
},
+107 -38
View File
@@ -5,6 +5,39 @@
"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..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "Xoá hồ sơ"
},
"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.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-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.create": {
"message": "Khởi tạo"
},
"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"
},
@@ -80,17 +101,53 @@
"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.mods.unknown-version": {
"message": "Không rõ"
"app.instance.worlds.add-server": {
"message": "Thêm máy chủ"
},
"app.instance.mods.updating": {
"message": "Đang cập nhật..."
"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.modal.install-to-play.content-required": {
"message": "Nội dung bắt buộc"
@@ -140,9 +197,6 @@
"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"
},
@@ -197,6 +251,24 @@
"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"
},
@@ -296,6 +368,12 @@
"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ỉ"
},
@@ -467,9 +545,6 @@
"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"
},
@@ -494,12 +569,6 @@
"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"
},
+110 -41
View File
@@ -5,6 +5,39 @@
"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": "输入整合包描述……"
},
@@ -41,35 +74,23 @@
"app.instance.confirm-delete.header": {
"message": "删除实例"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "这个整合包已经安装在\"{instanceName}\"实例中了。"
"app.instance.modpack-already-installed.body": {
"message": "整合包已经安装到实例<bold>{instanceName}</bold> 中了。你确定要重复安装吗?"
},
"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.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}”"
"message": "已添加 “{name}”"
},
"app.instance.mods.projects-were-added": {
"message": "已添加 {count} 个项目"
@@ -80,17 +101,53 @@
"app.instance.mods.share-title": {
"message": "分享整合包内容"
},
"app.instance.mods.show-file": {
"message": "显示文件"
},
"app.instance.mods.successfully-uploaded": {
"message": "上传成功"
},
"app.instance.mods.unknown-version": {
"message": "未知"
"app.instance.worlds.add-server": {
"message": "添加服务器"
},
"app.instance.mods.updating": {
"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": "内容需求"
@@ -140,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "默认实例选项"
},
"app.settings.tabs.feature-flags": {
"message": "功能开关"
},
"app.settings.tabs.java-installations": {
"message": "Java 管理"
},
@@ -197,6 +251,24 @@
"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": "添加好友"
},
@@ -213,7 +285,7 @@
"message": "这可能与对方的 Minecraft 用户名不同!"
},
"friends.add-friend.username.placeholder": {
"message": "输入 Modrinth 用户名…"
"message": "输入 Modrinth 用户名…"
},
"friends.add-friend.username.title": {
"message": "你好友的 Modrinth 用户名是什么?"
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "编辑存档"
},
"instance.files.adding-files": {
"message": "正在添加文件({completed}/{total}"
},
"instance.files.save-as": {
"message": "保存为……"
},
"instance.server-modal.address": {
"message": "地址"
},
@@ -363,7 +441,7 @@
"message": "名称"
},
"instance.settings.tabs.hooks": {
"message": "启动Hooks"
"message": "启动 Hooks"
},
"instance.settings.tabs.hooks.custom-hooks": {
"message": "自定义启动Hooks"
@@ -467,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "在主界面隐藏"
},
"instance.worlds.filter.available": {
"message": "可用"
},
"instance.worlds.game_already_open": {
"message": "实例已启动"
},
@@ -494,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "游玩实例"
},
"instance.worlds.type.server": {
"message": "服务器"
},
"instance.worlds.type.singleplayer": {
"message": "单人游戏"
},
"instance.worlds.view_instance": {
"message": "查看实例"
},
+108 -39
View File
@@ -5,6 +5,39 @@
"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": "輸入模組包描述..."
},
@@ -41,33 +74,21 @@
"app.instance.confirm-delete.header": {
"message": "刪除實例"
},
"app.instance.modpack-already-installed.admonition-body": {
"message": "這個模組包已經安裝在{instanceName}」實例中了。"
"app.instance.modpack-already-installed.body": {
"message": "模組包已經安裝在<bold>{instanceName}</bold> 實例中了,您確定要再次安裝嗎?"
},
"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.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}」"
},
@@ -80,17 +101,53 @@
"app.instance.mods.share-title": {
"message": "分享模組包內容"
},
"app.instance.mods.show-file": {
"message": "顯示檔案"
},
"app.instance.mods.successfully-uploaded": {
"message": "上傳成功"
},
"app.instance.mods.unknown-version": {
"message": "未知"
"app.instance.worlds.add-server": {
"message": "新增伺服器"
},
"app.instance.mods.updating": {
"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": "所需內容"
@@ -140,9 +197,6 @@
"app.settings.tabs.default-instance-options": {
"message": "預設實例選項"
},
"app.settings.tabs.feature-flags": {
"message": "功能開關"
},
"app.settings.tabs.java-installations": {
"message": "Java 安裝"
},
@@ -162,7 +216,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} 現已推出!由於你目前使用的是計量付費網路,我們並未自動下載更新。"
@@ -197,6 +251,24 @@
"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": "新增好友"
},
@@ -296,6 +368,12 @@
"instance.edit-world.title": {
"message": "編輯世界"
},
"instance.files.adding-files": {
"message": "正在新增檔案 ({completed}/{total})"
},
"instance.files.save-as": {
"message": "另存新檔..."
},
"instance.server-modal.address": {
"message": "位址"
},
@@ -467,9 +545,6 @@
"instance.worlds.dont_show_on_home": {
"message": "不要在首頁顯示"
},
"instance.worlds.filter.available": {
"message": "可用"
},
"instance.worlds.game_already_open": {
"message": "實例已經開啟"
},
@@ -494,12 +569,6 @@
"instance.worlds.play_instance": {
"message": "遊戲實例"
},
"instance.worlds.type.server": {
"message": "伺服器"
},
"instance.worlds.type.singleplayer": {
"message": "單人遊戲"
},
"instance.worlds.view_instance": {
"message": "檢視實例"
},
+3
View File
@@ -1,4 +1,5 @@
import 'floating-vue/dist/style.css'
import 'overlayscrollbars/overlayscrollbars.css'
import * as Sentry from '@sentry/vue'
import { VueScanPlugin } from '@taijased/vue-render-tracker'
@@ -8,6 +9,7 @@ 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'
@@ -50,5 +52,6 @@ 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
@@ -0,0 +1,26 @@
<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>
@@ -0,0 +1,28 @@
<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>
@@ -0,0 +1,29 @@
<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>
@@ -0,0 +1,26 @@
<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>
@@ -0,0 +1,159 @@
<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>
@@ -0,0 +1,7 @@
<script setup lang="ts">
import { ServersManageOverviewPage } from '@modrinth/ui'
</script>
<template>
<ServersManageOverviewPage />
</template>
@@ -0,0 +1,7 @@
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 }
+2 -1
View File
@@ -1,6 +1,7 @@
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, Skins, Worlds }
export { Browse, Index, Servers, Skins, Worlds }
@@ -0,0 +1,352 @@
<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>
+58 -43
View File
@@ -1,8 +1,12 @@
<template>
<div v-if="instance">
<div class="p-6 pr-2 pb-4" @contextmenu.prevent.stop="(event) => handleRightClick(event)">
<div v-if="instance" :class="{ 'flex h-full flex-col': isFixedRender }">
<div
:class="['p-6 pr-2 pb-4', { 'shrink-0': isFixedRender }]"
@contextmenu.prevent.stop="(event) => handleRightClick(event)"
>
<ExportModal ref="exportModal" :instance="instance" />
<InstanceSettingsModal
:key="instance.path"
ref="settingsModal"
:instance="instance"
:offline="offline"
@@ -36,27 +40,6 @@
</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>
@@ -134,9 +117,9 @@
</button>
</ButtonStyled>
<ButtonStyled v-else-if="playing === true" color="red" size="large">
<button @click="stopInstance('InstancePage')">
<button :disabled="stopping" @click="stopInstance('InstancePage')">
<StopCircleIcon />
Stop
{{ stopping ? 'Stopping...' : 'Stop' }}
</button>
</ButtonStyled>
<ButtonStyled
@@ -192,7 +175,7 @@
color="brand"
size="large"
>
<button disabled>Loading...</button>
<button disabled>Starting...</button>
</ButtonStyled>
<ButtonStyled circular size="large">
<button v-tooltip="'Instance settings'" @click="settingsModal?.show()">
@@ -225,21 +208,17 @@
</template>
</ContentPageHeader>
</div>
<div class="px-6">
<div :class="['px-6', { 'shrink-0': isFixedRender }]">
<NavTabs :links="tabs" />
</div>
<div v-if="!!instance" class="p-6 pt-4">
<div :class="['p-6 pt-4', { 'min-h-0 flex-1 overflow-y-auto': isFixedRender }]">
<RouterView
v-if="route.path.startsWith('/instance')"
v-slot="{ Component }"
:key="instance.path"
>
<template v-if="Component">
<Suspense
:key="instance.path"
@pending="loadingBar.startLoading()"
@resolve="loadingBar.stopLoading()"
>
<Suspense :key="instance.path">
<component
:is="Component"
:instance="instance"
@@ -252,9 +231,6 @@
@play="updatePlayState"
@stop="() => stopInstance('InstanceSubpage')"
></component>
<template #fallback>
<LoadingIndicator />
</template>
</Suspense>
</template>
</RouterView>
@@ -285,6 +261,7 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
BoxesIcon,
CheckCircleIcon,
ClipboardCopyIcon,
DownloadIcon,
@@ -302,6 +279,7 @@ import {
ServerIcon,
SettingsIcon,
StopCircleIcon,
TerminalSquareIcon,
UpdatedIcon,
UserPlusIcon,
XIcon,
@@ -311,13 +289,14 @@ import {
ButtonStyled,
ContentPageHeader,
injectNotificationManager,
LoadingIndicator,
NavTabs,
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'
@@ -329,7 +308,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 NavTabs from '@/components/ui/NavTabs.vue'
import { useInstanceConsole } from '@/composables/useInstanceConsole'
import { trackEvent } from '@/helpers/analytics'
import { get_project_v3 } from '@/helpers/cache.js'
import { process_listener, profile_listener } from '@/helpers/events'
@@ -337,16 +316,17 @@ 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 } from '@/helpers/worlds'
import { get_server_status, refreshWorlds } from '@/helpers/worlds'
import { injectServerInstall } from '@/providers/server-install'
import { handleSevereError } from '@/store/error.js'
import { useBreadcrumbs, useLoading } from '@/store/state'
import { useBreadcrumbs } from '@/store/state'
dayjs.extend(duration)
dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const { playServerProject } = injectServerInstall()
const queryClient = useQueryClient()
const route = useRoute()
const router = useRouter()
@@ -363,6 +343,7 @@ 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>>()
@@ -405,6 +386,14 @@ async function fetchInstance() {
}
fetchDeferredData()
if (instance.value) {
queryClient.prefetchQuery({
queryKey: ['worlds', instance.value.path],
queryFn: () => refreshWorlds(instance.value!.path),
staleTime: 30_000,
})
}
}
function fetchDeferredData() {
@@ -447,18 +436,39 @@ watch(
const basePath = computed(() => `/instance/${encodeURIComponent(route.params.id as string)}`)
/**
* Per-route layout mode.
* - `'scroll'` (default): the whole instance page scrolls inside `.app-viewport`. This lets
* `position: sticky` children (and the viewport-rooted `IntersectionObserver` used by
* `useStickyObserver`) work correctly.
* - `'fixed'`: the header + tabs are pinned and only the tab body scrolls in its own container.
* Used by tabs whose content (e.g. the log console) needs a bounded height to resolve `h-full`.
*/
const renderMode = computed<'scroll' | 'fixed'>(() =>
route.meta.renderMode === 'fixed' ? 'fixed' : 'scroll',
)
const isFixedRender = computed(() => renderMode.value === 'fixed')
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,
},
])
@@ -476,8 +486,6 @@ if (instance.value) {
})
}
const loadingBar = useLoading()
const options = ref<InstanceType<typeof ContextMenu> | null>(null)
const startInstance = async (context: string) => {
@@ -504,8 +512,10 @@ const startInstance = async (context: string) => {
}
const stopInstance = async (context: string) => {
playing.value = false
stopping.value = true
await kill(route.params.id as string).catch(handleError)
stopping.value = false
playing.value = false
if (!instance.value) return
trackEvent('InstanceStop', {
@@ -654,6 +664,11 @@ const timePlayedHumanized = computed(() => {
onUnmounted(() => {
unlistenProcesses()
unlistenProfiles()
const profilePath = route.params.id
if (profilePath) {
const { destroy } = useInstanceConsole(profilePath)
destroy()
}
})
</script>
+154 -490
View File
@@ -1,127 +1,24 @@
<template>
<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>
<div class="flex flex-col gap-4 h-full">
<ConsolePageLayout />
</div>
</template>
<script setup>
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { CheckIcon, ClipboardCopyIcon, SearchIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
import {
Button,
Card,
Checkbox,
DropdownSelect,
ConsolePageLayout,
injectModrinthClient,
injectNotificationManager,
StyledInput,
provideConsoleManager,
} from '@modrinth/ui'
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 { computed, onUnmounted, ref, shallowRef, triggerRef, watch, watchEffect } from 'vue'
import { useRoute } from 'vue-router'
import { RecycleScroller } from 'vue-virtual-scroller'
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)
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'
const client = injectModrinthClient()
const { handleError } = injectNotificationManager()
const route = useRoute()
@@ -158,414 +55,181 @@ const props = defineProps({
},
})
const currentLiveLog = ref(null)
const currentLiveLogCursor = ref(0)
const emptyText = ['No live game detected.', 'Start your game to proceed.']
const profilePathId = computed(() => route.params.id)
const {
liveConsole,
historicalConsole,
hydrate,
getHistoricalLogs,
getHistoricalContent,
invalidate,
clearLive,
} = useInstanceConsole(profilePathId.value)
const logs = ref([])
await setLogs()
await hydrate()
const logsColored = true
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 selectedLogIndex = ref(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 isLive = computed(() => selectedLogIndex.value === 0)
const levels = ['Comment', 'Error', 'Warn', 'Info', 'Debug', 'Trace']
const levelFilters = ref({})
levels.forEach((level) => {
levelFilters.value[level.toLowerCase()] = true
const filteredLogs = computed(() =>
props.playing ? logs.value.filter((l) => l.live || l.name !== 'latest.log') : logs.value,
)
const logSources = computed(() =>
filteredLogs.value.map((l, i) => ({
id: String(i),
name: l?.name ?? `Log ${i}`,
live: l?.live ?? false,
})),
)
const activeConsole = computed(() => (isLive.value ? liveConsole : historicalConsole))
const logLines = shallowRef(activeConsole.value.output.value)
watchEffect(() => {
logLines.value = activeConsole.value.output.value
triggerRef(logLines)
})
const searchFilter = ref('')
function shouldDisplay(processedLine) {
if (!processedLine.level) {
return true
}
const crashAnalysis = ref(null)
if (!levelFilters.value[processedLine.level.toLowerCase()]) {
return false
}
if (searchFilter.value !== '') {
if (!processedLine.text.toLowerCase().includes(searchFilter.value.toLowerCase())) {
return false
async function analyseForCrash() {
const lines = liveConsole.output.value
if (lines.length === 0) return
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
}
} catch {
// Crash analysis is best-effort
}
return true
}
// 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 selectedLog = computed(() => filteredLogs.value[selectedLogIndex.value])
const deleteDisabled = computed(() => {
const log = selectedLog.value
if (!log || log.live) return true
return log.filename === 'latest.log' && props.playing
})
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\]))/
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
})
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
}
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 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)
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)
}
})
if (logs.value.length > 1 && !props.playing) {
selectedLogIndex.value = 1
} else {
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)
selectedLogIndex.value = 0
}
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()
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 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 clearLiveLog = async () => {
currentLiveLog.value = ''
// does not reset cursor
}
const unlistenLog = await log_listener((payload) => {
if (payload.profile_path_id !== profilePathId.value) return
const isLineLevel = (text, level) => {
if ((text.includes('/INFO') || text.includes('[System] [CHAT]')) && level === 'info') {
return true
if (payload.type === 'log4j') {
liveConsole.addLog4jEvent(payload)
} else if (payload.type === 'legacy') {
liveConsole.addLegacyLog(payload.message)
}
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') {
currentLiveLog.value = ''
currentLiveLogCursor.value = 0
liveConsole.clear()
invalidate()
selectedLogIndex.value = 0
}
if (e.event === 'finished') {
currentLiveLog.value = ''
currentLiveLogCursor.value = 0
userScrolled.value = false
await setLogs()
selectedLogIndex.value = 1
invalidate()
const freshLogs = await getHistoricalLogs(props.instance.path)
logs.value = buildLogList(freshLogs)
void analyseForCrash()
}
})
onMounted(() => {
logContainer.value.$el.addEventListener('scroll', handleUserScroll)
})
onBeforeUnmount(() => {
logContainer.value.$el.removeEventListener('scroll', handleUserScroll)
})
onUnmounted(() => {
clearInterval(interval.value)
unlistenLog()
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>
+106 -126
View File
@@ -1,71 +1,76 @@
<template>
<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>
<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>
</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,
@@ -79,10 +84,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'
@@ -125,10 +130,6 @@ 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',
@@ -141,30 +142,10 @@ 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
@@ -283,7 +264,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
@@ -301,7 +282,7 @@ async function _toggleDisableMod(mod: ContentItem) {
}
}
const toggleDisableMod = useDebounceFn(_toggleDisableMod, 20)
const toggleDisableDebounced = useDebounceFn(toggleDisableMod, 20)
async function removeMod(mod: ContentItem) {
await remove_project(props.instance.path, mod.file_path!).catch(handleError)
@@ -354,6 +335,12 @@ 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)
@@ -364,20 +351,12 @@ 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()
}
}
@@ -468,11 +447,11 @@ async function handleSwitchVersion(item: ContentItem) {
}
async function handleModpackContentToggle(item: ContentItem) {
await toggleDisableMod(item)
await toggleDisableDebounced(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() {
@@ -539,7 +518,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)
await fetchAndSpliceVersion(version.id, 'must_revalidate', handleError as (err: unknown) => void)
loadingChangelog.value = false
}
@@ -660,14 +639,14 @@ function getOverflowOptions(item: ContentItem): OverflowMenuOption[] {
const options: OverflowMenuOption[] = []
options.push({
id: formatMessage(messages.showFile),
id: formatMessage(commonMessages.showFileButton),
icon: FolderOpenIcon,
action: () => highlightModInProfile(props.instance.path, item.file_path),
})
if (item.project?.slug) {
options.push({
id: formatMessage(messages.copyLink),
id: formatMessage(commonMessages.copyLinkButton),
icon: ClipboardCopyIcon,
action: async () => {
await navigator.clipboard.writeText(
@@ -718,18 +697,13 @@ 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
})
.map((cat: { name: string }) => ({
...cat,
name: cat.name.charAt(0).toUpperCase() + cat.name.slice(1),
}))
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
})
} else {
linkedModpackCategories.value = []
}
@@ -799,8 +773,8 @@ provideContentManager({
hasUpdate: linkedModpackHasUpdate.value,
disabled: isModpackUpdating.value,
disabledText: isModpackUpdating.value
? formatMessage(messages.updating)
: formatMessage(messages.installing),
? formatMessage(commonMessages.updatingLabel)
: formatMessage(commonMessages.installingLabel),
}
: null,
),
@@ -808,13 +782,18 @@ provideContentManager({
isBusy: isInstanceBusy,
isBulkOperating,
contentTypeLabel: ref(formatMessage(messages.contentTypeProject)),
toggleEnabled: toggleDisableMod,
bulkEnableItems: (items) =>
Promise.all(items.map((item) => _toggleDisableMod(item))).then(() => {}),
bulkDisableItems: (items) =>
Promise.all(items.map((item) => _toggleDisableMod(item))).then(() => {}),
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(
() => {},
),
deleteItem: removeMod,
bulkDeleteItems: (items) => Promise.all(items.map((item) => removeMod(item))).then(() => {}),
bulkDeleteItems: (items: ContentItem[]) =>
Promise.all(items.map((item) => removeMod(item))).then(() => {}),
refresh: () => initProjects('must_revalidate'),
browse: handleBrowseContent,
uploadFiles: handleUploadFiles,
@@ -830,7 +809,7 @@ provideContentManager({
showContentHint,
dismissContentHint,
shareItems: handleShareItems,
mapToTableItem: (item) => ({
mapToTableItem: (item: ContentItem) => ({
id: item.id,
project: item.project ?? {
id: item.file_name,
@@ -843,7 +822,7 @@ provideContentManager({
: undefined,
version: item.version ?? {
id: item.file_name,
version_number: formatMessage(messages.unknownVersion),
version_number: formatMessage(commonMessages.unknownLabel),
file_name: item.file_name,
},
versionLink:
@@ -860,6 +839,7 @@ 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