Compare commits

...
Author SHA1 Message Date
coolbot100s 9f11c210f0 fix linebreak issues in quick replies 2026-07-10 18:24:18 -07:00
coolbot100s 47103ce3e9 Move quick reply definitions 2026-07-10 18:18:15 -07:00
coolbot100s da775bd1c1 handle non commercially licensed external content. 2026-07-10 18:02:58 -07:00
coolbot100s f5072c917a Add new externals quick replies 2026-07-10 15:26:53 -07:00
ThatGravyBoatandGitHub 8680e61883 chore: misc moderation changes (#6690)
* fix: tech card not updating project status correctly

* feat: pull out quick actions into buttons for reports

* fix: reports duplicating in the reports queue

* feat: allow for moderation keybinds to be rebindable

* run prepr
2026-07-10 20:25:39 +00:00
Prospector ea86db450b changelog 2026-07-10 10:43:00 -07:00
ProspectorandGitHub bbdaa171e3 fix: remove unused facets from documentation (#6685) 2026-07-10 10:37:48 -07:00
ProspectorandGitHub af8daca059 fix: make search revalidate & change cache to be 10 minutes (#6682)
* fix: make search revalidate & change cache to be 10 minutes

* prepr
2026-07-10 16:58:45 +00:00
Truman GaoandGitHub 2434f0c45b fix: ads consent close button color (#6678)
* fix: ads consent close button

* update color
2026-07-10 16:57:56 +00:00
332dfe1074 Fix: fix passkey translation keys (#6512)
* fix: common message mentioning passkey

* devex: split title and header translation keys

* chore: rerun pnpm prepr:frontend

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-07-10 16:35:59 +00:00
Calum H.andGitHub c07727aa49 feat: install flow improvements (#6669)
* feat: better error handling + copy details btn for info

* refactor: clean up error handling into diagnostics

* feat: extra info for failure states + queuing properly

* fix: cleanup

* fix: lint

* fix: fmt

* fix: cleanup

* fix: cleanup

* fix: lint

* feat: use bon builder
2026-07-10 11:54:56 +00:00
aecsocketandGitHub 57a977f7f3 feat: use v1 version manifest for Quilt (#6650)
* wip: app-side logic

* lints

* fmt

* fmt

* Split Daedalus changes out of frontend branch

* Remove Daedalus changes from frontend branch
2026-07-10 11:17:41 +00:00
NitrrineandGitHub 323d088ebd fix: light mode icon shows in dark mode (#6614)
* fix: light mode icon shows in dark mode

* feat: use os icons from assets repo

* revert initial changes

* fix: lint
2026-07-09 21:43:13 +00:00
Truman GaoandGitHub 46f94bd067 fix: download modal no compatible empty state (#6680) 2026-07-09 21:37:09 +00:00
aecsocketandGitHub e877167db7 feat: global trace database frontend + improvements to tech review status sync (#6671)
* wip

* ui edits

* better logic for syncing tech review state in a single centralized place

* never automatically finish a tech review report

* fix css

* fix clippy

* prepr
2026-07-09 16:09:02 +00:00
Prospector 623b51e6ea update ads.txt 2026-07-09 08:37:23 -07:00
Prospector 5face9e56a changelog 2026-07-08 15:58:31 -07:00
40aff1e8bb feat: download modal changes (#6652)
* update download with modrinth app button

* feat: implement compatible version card and update download dependency buttons

* fix: download multiple files immediately triggers blocking

* refactor: implement download modal provider

* fix: duplicate dependencies and style update for dependecy section

* feat: improve loading flow when there are dependencies, to not show compatible version until all data is loaded

* fix: fallback to download compatible version if dependency lookup fails

* feat: implement selecting compatible versions when there are multiple

* fix: update dependencies version table to any compatible version

* fix: version showing before depedencies loaded

* feat: add platform options sort order

* pnpm prepr

* feat: keep previous result showing until new one loads in when change compatibility options

* feat: implement better handling for showing required/recommended resource packs with additional files

* pnpm prepr

* fix: required resource pack not showing footer actions

* fix: download with deps is downloading all files

* feat: add joined button for downloading with recommended

* feat: add admontion for dependency that has resource pack to download

* pnpm prepr

* fix: lint

* feat: append "-EXTRACT_ME.zip" to zipped project with dependencies

* fix: date published capitalization

* pnpm prepr

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-07-08 22:54:23 +00:00
SychicandGitHub 5f8837604d feat(labrinth): shared instance icon (#6665) 2026-07-08 22:45:15 +00:00
ProspectorandGitHub 795dad040f feat: add complementary installer source support (#6667) 2026-07-08 15:44:59 -07:00
Truman GaoandGitHub 9ca181dc1a fix: privacy popup in app bounded to tiny webview (#6657) 2026-07-08 16:29:41 +00:00
aecsocketandGitHub c84b658e41 feat: tech review global trace database (#6648)
* global detail verdicts system

* global traces frontend

* split global routes into own mod

* tweak migration to only do no-key check at app level

* prepr
2026-07-08 16:06:45 +00:00
Prospector 9006dce2b0 changelog 2026-07-08 08:06:13 -07:00
ProspectorandGitHub efdfb51149 fix: add DEI (downloads, equality, and inequality) (#6664)
fix: add DEI
2026-07-08 15:04:21 +00:00
ProspectorandGitHub 79cc5fef72 feat: exclude project types filter (#6660)
* feat: exclude project types filter

* fix merge issue
2026-07-08 14:30:47 +00:00
Truman GaoandGitHub ddefc28ca6 fix: instance edits not emitting edit event to reload state (#6658) 2026-07-08 09:16:32 +00:00
654757f9fb docs: replace broken loc count with static count (#6615)
* docs: replace broken loc count with static count

Signed-off-by: Nitrrine <43351072+Nitrrine@users.noreply.github.com>

* feat: update to use loctopus api

credits to @creeperkatze <3

Signed-off-by: Nitrrine <43351072+Nitrrine@users.noreply.github.com>

---------

Signed-off-by: Nitrrine <43351072+Nitrrine@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-07-08 05:21:47 +00:00
François-Xavier TalbotandGitHub 407641ee7c fix(labrinth): serde(default) new search document fields (#6656) 2026-07-07 15:28:54 -07:00
ProspectorandGitHub 96ab120cd0 feat: add all_project_types to search results (#6654)
* feat: add all_project_types to search results

* update docs
2026-07-07 21:30:42 +00:00
François-Xavier TalbotandGitHub 3ed75a146a fix(labrinth): fix routing config for /v3/content (#6649) 2026-07-07 16:48:14 +00:00
aecsocketandGitHub 5a1c2a46bc Generate different Daedalus Quilt templates for 26.x/non-26.x (#6554)
* Generate different Daedalus Quilt templates for 26.x/non-26.x

* add docs

* wip: daedalus version groups

* wip: app-side logic

* finish app-side logic, fix certain libs not included

* lints

* fmt

* fmt

* Split frontend changes out of Daedalus branch
2026-07-07 16:45:33 +00:00
ProspectorandGitHub df8ebbd3e0 feat: add /build route (#6644) 2026-07-07 15:43:26 +00:00
François-Xavier TalbotandGitHub 517c3d2d72 fix(labrinth): fix prefix stripping in mrpack scanning (#6646) 2026-07-07 01:31:07 +00:00
Prospector 7f15772f59 changelog 2026-07-06 15:19:29 -07:00
ProspectorandGitHub 0905ea72f6 fix: project page in app not receiving instance context & port version page to app (#6618)
* fix: project page in app not receiving instance context

* prepr

* feat: port version page to app

* commonize keys

* whoops bring those back
2026-07-06 15:17:25 -07:00
8d20fd82db fix: moderation/projects endpoint slow (#6639)
* fix: moderation/projects endpoint slow

* fix: re-add tech rev toggle off by default

* fix: i18n

* prepare

---------

Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
2026-07-06 19:16:52 +00:00
aecsocketandGitHub c831e38e32 fix: file scans only consider published files as on-platform (#6638) 2026-07-06 18:13:21 +00:00
Calum H.andGitHub 40b8fb3a4a fix: use tanstack for server pinging (#6631) 2026-07-06 18:09:51 +00:00
François-Xavier TalbotandGitHub 977bb2ff58 ci: fix (#6640)
* ci: use apt

* fix(ci): sudo

* bruhj
2026-07-06 17:51:09 +00:00
Calum H.andGitHub 732bdfc79b fix: warm middleware on server-side (#6636) 2026-07-06 17:39:43 +00:00
Calum H.andGitHub 1cce1eb248 fix: strip runtime filters to be authoratative (#6635) 2026-07-06 17:39:17 +00:00
Calum H.andGitHub 76320f227f fix: dont prefetch file contents if it's not editable/viewable (#6632) 2026-07-06 17:06:41 +00:00
Calum H.andGitHub 34228a5b6c fix: use next tick in delete confirm (#6633) 2026-07-06 17:06:10 +00:00
Calum H.andGitHub 6d66aee4ec fix: properly select correct databack versions (#6634) 2026-07-06 16:59:49 +00:00
SychicandGitHub fc7be043c7 fix(labrinth): add invited_by field (#6625) 2026-07-06 16:33:00 +00:00
7fa88e5d4d feat: moderation modpack scan button (#6627)
* feat: moderation modpack scan button

* fix: ui

* fix: lint

---------

Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-07-06 13:19:39 +00:00
ThatGravyBoatandGitHub 21508e0637 feat: move tech quick actions to dedicated buttons (#6628) 2026-07-06 08:31:51 +00:00
Prospector ad4912d64d changelog + one more fix 2026-07-04 19:09:58 -07:00
ProspectorandGitHub 0002ae2479 fix: env filter showing in hosting search (#6612) 2026-07-04 18:47:30 -07:00
Prospector e4568330a7 changelog 2026-07-04 16:16:02 -07:00
ProspectorandGitHub b0108e0624 fix: environment filters (#6611) 2026-07-04 16:15:28 -07:00
Prospector ac63abadb7 changelog 2026-07-04 16:00:11 -07:00
fa749b5aff fix: respect height attribute on images in markdown body (#6506)
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
2026-07-04 22:47:17 +00:00
a2adbe18c4 Request: Add cdn/workflow.serilum.com to allowedHostnames whitelist to bypass wsrv.nl caching (#6567)
* Add cdn/workflow.serilum.com to allowedHostnames whitelist

Signed-off-by: Rick South <rick@natamus.com>

* Update parse.ts

Signed-off-by: Prospector <6166773+Prospector@users.noreply.github.com>

---------

Signed-off-by: Rick South <rick@natamus.com>
Signed-off-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-07-04 22:40:34 +00:00
Truman GaoandGitHub 0fcbe51816 fix: version table overflow issues (#6583)
* feat: shorten ModLoader

* fix: game version and platform tag overflow issues

* feat: add table version row below and move version files to version row below

* fix: platform plural

* pnpm prepr
2026-07-04 22:06:09 +00:00
Truman GaoandGitHub 23cfeca91d feat: project download modal (#6602)
* refactor: componentize project download modal

* feat: implement new project download modal

* fix: button trigger placeholder text styles

* feat: dependency download button

* refactor: move project download modal into folder

* refactor: separate out download dependencies section

* refactor: split out download project and install with modrinth app sections

* feat+refactor: add nested dependencies route and move logic into related subcomponents

* feat: handle download project dependencies recursively and properly pass all game versions/platforms compatible on the selected version

* feat: if only dependency project is listed, find latest compatible dependency version to download in version page

* refactor: use ts and also have modal own get project/versions queries

* feat: implement opening project download modal in place in version page

* feat: implement incompatible options due to base project

* feat: auto select loader after selecting a game version, if only one loader is left compatible

* feat: add prop to stop route selection being persisted in url param

* pnpm prepr

* feat: update tooltip copy

* feat: polish some styles

* fix: fabric dependency showing when quilt is selected

* fix: tooltip hover area

* fix: when there are multiple compatible of same game version + loader, give latest

* feat: add link to version page

* feat: add truncated tooltip

* pnpm prepr

* feat: add warning icon for incompatible selection

* feat: polish loading state and tooltip

* feat: implement download all dependencies

* pnpm prepr

* fix: light theme card

* fix: additional files label

* refactor: move additional files into download dependencies section

* feat: move download all button in heading with compatible version

* fix color

* fix: dependencies not appearing when open modal second time

* fix: dependency lines when 3 nested

* feat: remove duplicated dependencies

* feat: add download count indicateor on download all button

* feat: add tooltip for downloading file with filename and size

* pnpm prepr

* feat: only show duplicate tooltip if there are duplicates
2026-07-04 21:45:34 +00:00
Truman GaoandGitHub db830ef65c feat: prevent .mrpack being uploaded as additional file (#6582) 2026-07-04 21:35:08 +00:00
ArthurandGitHub 08f44b3582 fix: make update pill button size to content clean (#6574)
* Fix update pill sizing

* Refactor as component
2026-07-04 21:33:36 +00:00
ArthurandGitHub 8ffc4e2599 chore: add modfolio to allowed hostnames (#6584)
Add modfolio to allowed hostnames
2026-07-04 21:33:24 +00:00
coolbotandGitHub 58a5619073 add alt keybind for resetting moderation progress. (#6606) 2026-07-04 21:28:08 +00:00
4a6fa9fc3d feat: better api docs (#6586)
* feat: docs

* fix tombi

* chore: fix some of the routes rendering with missing /

* response schemas

* fix: restore labrinth docs routes

* Fix path parameter docs in routes

* remove utoipa-actix-web

* consistency

* improve version intros

* improve formatting, examples

* better hash examples, move openapi stuff to openapi.rs

* more utoipa param fixes

* request body docs

* chore: remove moderation route from v2, remove ingest & webhooks from v3 spec

* fixes

* chore: tweak sources titles

* fix

* fix test

* improve examples

* increase compiler spawned thread stack size

* remove unused tests & script

* test

* bro what

* fix

---------

Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
2026-07-04 14:19:21 +00:00
aecsocketandGitHub b26d048a63 Remove AutoMod code (#6604)
* 🦀 automod is gone 🦀

* prepare
2026-07-03 21:32:11 +00:00
François-Xavier TalbotandGitHub d5be2bd739 fix(labrinth): include SharedInstanceInvite notifications for ws (#6603)
* fix(labrinth): include SharedInstanceInvite notifications for ws

* chore: fmt
2026-07-03 17:06:58 +00:00
Prospector 0dac5684af changelog 2026-07-03 08:16:29 -07:00
coolbotandGitHub ca0b8d5ca4 add excessive languages warning nag (#6587) 2026-07-03 12:12:07 +00:00
coolbotandGitHub b6651efe52 Moderation Fix: Handle more license cases (#6590)
Apply correct behavior in edge cases
2026-07-02 17:26:10 +00:00
aecsocketandGitHub eb28121c0a fix: scan logic file whitelist + rescan route (#6585)
* explicitly only scan jar/zip/disabled files for attributions

* add route to force scan file

* prepare
2026-07-02 16:33:16 +00:00
SychicandGitHub a5ebc85356 refactor(labrinth): combine logic for external notification endpoints (#6578)
* refactor(labrinth): combine logic for external notification endpoints

also introduce new notification that doesn't send an email

* style(labrinth): derive default

* feat(labrinth): return more detailed failure information for external
notifications
2026-07-02 15:44:47 +00:00
Prospector c530671be1 fix collection widget on mobile 2026-07-01 18:09:12 -07:00
Prospector bb6d8a6fc4 changelog 2026-07-01 17:44:29 -07:00
ProspectorandGitHub 256e8f946a chore: pride month fundraiser conclusion (#6588)
* pride end blog post

* Fix drago link

* prepr oops
2026-07-01 17:43:44 -07:00
Calum H.andGitHub 7e5590b8b9 fix: improve observability during fetch failure (#6555)
* fix: improve observability during fetch failure

+ remove analytics log spam

* fix: rev comments
2026-07-01 15:58:20 +00:00
SychicandGitHub d20f05fb04 feat(labrinth): instance share notification (#6581) 2026-07-01 14:21:56 +00:00
Truman GaoandGitHub a00a7971d1 feat: add tooltip for unknown dependents (#6572) 2026-07-01 05:21:42 +00:00
aecsocketandGitHub 26e98b7155 fix: change Tiltify campaign errors to quieter log level v2 (#6576)
import
2026-06-30 21:36:47 +02:00
aecsocketandGitHub 77b06f679e fix: change Tiltify campaign errors to quieter log level (#6575) 2026-06-30 21:34:37 +02:00
Truman GaoandGitHub 50e9472dd9 fix: cannot set owner for creating projects (#6573) 2026-06-30 17:00:10 +00:00
ThatGravyBoatandGitHub b569354793 fix: packs not being validated correctly (#6533) 2026-06-30 14:02:58 +00:00
Michael H. b667b3ae9e fix: remove sccache s3 2026-06-30 11:41:08 +02:00
coolbotandGitHub 16de6182e4 Add nag for custom license info. (#6564)
* add nag for custom license info

* preppr
2026-06-30 02:25:13 +00:00
Calum H.andGitHub 364fed52dd feat: changelog (#6563) 2026-06-30 01:57:11 +02:00
Calum H.andGitHub 0fe695721a fix: try fix app freezing on instance page load (#6562)
* fix: app freezing fix

* fix: fmt
2026-06-30 01:55:55 +02:00
Calum H.andGitHub 692f22b749 feat: changelog 0.15.3 (#6560)
feat: changlog 0.15.3
2026-06-30 00:54:16 +02:00
Calum H.andGitHub ed7792097d fix: better handling of larger instances for ExportModal (#6559) 2026-06-30 00:51:25 +02:00
Prospector c56ce6c9ee changelog 2026-06-29 13:45:04 -07:00
Calum H.andGitHub 91bc58b8ee fix: cleanup stale db entry on rename instance file (#6549)
fix: race on renaming instance file
2026-06-29 17:36:46 +00:00
Truman GaoandGitHub 38d757b66a fix: analytics colour pick is not excluding unknown (#6552)
* fix: analytics colour picking not excluding unknown

* feat: improve colours, better accessibility
2026-06-29 17:17:03 +00:00
Calum H.andGitHub 862b54f00d refactor: instance export modal (#6543)
* refactor: export modal w the design system

* fix: lint

* fix: rev

* fix: lint

* fix: qa
2026-06-29 17:01:00 +00:00
Calum H.andGitHub c2f4835fe7 fix: spacing + friends (#6550) 2026-06-29 17:00:59 +00:00
ThatGravyBoatandGitHub b97c191d75 fix(docs): create version endpoint missing file_types field (#6551) 2026-06-29 16:22:08 +00:00
Truman GaoandGitHub b0c6a06909 feat: update versions table (#6502)
* feat: add new versions table, move environments column to be feature flag toggled, and add header/cell class props to Table.vue

* feat: implement much better table column widths

* feat: update version filter control to use multiselect

* feat: implement clicking table row to go to version

* feat: mobile pass on versions table

* feat: use multiselect for platform, and fix game version show all versions when dont exist

* pnpm prepr

* feat: replace dashboard projects table with new table

* feat: update projects so its out of card

* fix: version actions buttons column width

* feat: implement overflow on loaders cell

* update platform and version width

* feat: update project icon

* feat: change edit links to use floating action bar

* pnpm prepr
2026-06-29 15:01:07 +00:00
ThatGravyBoatandGitHub fc3e384f54 fix: tech review card not refreshing on status change (#6508)
fix: tech rev card not refreshing on status change
2026-06-29 14:40:20 +00:00
Calum H.andGitHub 87ff8fd4d9 fix: server panel not using correct endpoint for mrpack reupload (#6539)
fix: server panel not using correct endpoint for local modpack reupload
2026-06-29 14:39:03 +00:00
Calum H.andGitHub 288c1fb970 fix: reduce ping timeout in app to 5 sec not 30 sec (#6545)
* fix: reduce ping timeout in app to 5 sec not 30 sec

* feat: readd comment
2026-06-29 14:38:51 +00:00
Calum H.andGitHub f6e6ac28d4 fix: change fs watcher to rely more on instance ids than path (#6547)
* fix: change fs watcher to rely more on instance ids than path

* fix: fmt
2026-06-29 14:38:44 +00:00
Calum H.andGitHub 4a972bca73 fix: change logic when selecting filters in Worlds.vue (#6548) 2026-06-29 14:38:37 +00:00
Calum H.andGitHub b09c26f616 fix: console sizing issues (#6540)
fix: console sizing issues (for good)
2026-06-29 14:38:30 +00:00
ArthurandGitHub 204bd52a5a fix: modpack content modal toggle state (#6532)
Fix modpack content modal toggle state
2026-06-29 14:04:55 +00:00
Calum H.andGitHub b59f327b04 feat: add chaos cubed skins (#6463) 2026-06-29 12:18:15 +00:00
Modrinth BotandGitHub 89e643fa50 New translations from Crowdin (main) (#6542) 2026-06-29 12:04:10 +00:00
Calum H.andGitHub cee25fdbbc fix: unlinking local mrpack does not show content afterwards (#6541) 2026-06-29 11:58:43 +00:00
Calum H.andGitHub b95a2d875a feat: changelog app 0.15.1 (#6522) 2026-06-26 16:42:19 +00:00
Calum H.andGitHub 1067b02830 fix: logs using id not path (#6511)
* fix: logs using id not path

* fix: lint
2026-06-26 17:50:26 +02:00
Calum H.andGitHub ebf4a6ef5f fix: bulk update updating linked content (#6517)
* fix: bulk update updating linked content

* fix: disabled content sometimes desyncing

* fix: bad filter

* fix: fmt
2026-06-26 17:50:15 +02:00
Calum H.andGitHub e4376c2b15 fix: clamp negative playtime (#6519)
* fix: clamp negative playtime

* fix: remove triggers use instant
2026-06-26 17:50:03 +02:00
Michael H. 5fe07de187 chore: bump mold and add mold to other jobs 2026-06-26 17:22:46 +02:00
Prospector 80d66d3c6e update ads.txt 2026-06-25 21:13:42 -07:00
Calum H.andGitHub 64f7fb811e feat: changelog 25th june (#6507) 2026-06-25 23:25:07 +02:00
734720e11e feat: instances v2 (#6431)
* feat: base of instances v2

* feat: use old profiles with compat layer

* prototype: instances v2

* fix: install_from using profile

* fix: skins migration fix

* fix: frontend still using profile path

* fix: add update proj multiselect guard

* fix: cargo fmt

* fix: content missing fields

* feat: break up app-lib/api/instance.rs

* fix: check_content_updates mismatch

* fix: updater modal cleanup w/new structure

* feat: better update all handling

* fix: remove preview_update_all

* fix: feedback on bulk update + lint

* fix: rem transitions

* fix: change to jsonb

* feat: app db backup after update

* fix: lint

* fix: sqlx prepare + use sqlx macros

* fix: lint

* fix: bugs

* feat: defuck the installing process up

* fix: bug of hell

* fix: shear

* fix: fmt

* fix: install progress spacing + change mc/content/overrides to bytes

* fix: lint

* fix: prepr

* fix: navtabs anim not working in app

* fix: worlds.vue improvements + browse page fixes

* feat: optimise queries + adapter fns

* fix: lint

* fix: lint

* feat: shared modrinth-content-management crate (#6469)

* feat: disable warnings setting

* feat: add instances shortcuts (#6329)

* Add modrinth://launch deep link to start a profile

Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck.

* Change route to /launch/profile/{id} for future extensibility

* fix: ensure profile path is url decoded

* fix: URL-decode profile path from deep link

* fix: use urlencoding crate for URL decoding

* feat: implement app instance shortcuts

* feat: change windows shortcut creation to use windows api instead

* feat: implement creating a shortcut launching world/server

* format

* fmt

* fix multiline inline tables

* pnpm prepr

* feat: move create shortcut to last item

* refactor: split up shortcuts.rs for individual platforms

* refactor: turn profile launch url into url type

* use string literal and add safety comment

* pt2

* refactor: rename anything that's profile into instance

* update mac shortcut

---------

Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>

---------

Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
2026-06-25 21:19:29 +00:00
ef4044534f feat: update auth flow (#5790)
* Backend routes for choosing username in OAuth flow

* fix up oauth flow routes

* improve URL-related OAuth code

* Use user-provided callback addr instead of SELF_ADDR

* Revert "Use user-provided callback addr instead of SELF_ADDR"

This reverts commit 7ea0635d86.

* fix flow

* fix: backend response for create oauth account

* feat: new auth flow (#5840)

* update auth with new designs

* refactor: auth.js to auth.ts

* refactor: componentize auth pages

* fix: auth pages height

* feat: initial implementation of new sign-in oauth

* fix create account flow

* fix checkbox

* remove hard coded username

* implement create user validation endpoint and add more specific error responses

* feat: implement under 13 DOB guard and email/password validation route

* fix: TOCTOU issue

* refactor: pnpm prepr

* fix: make sure staging uses staging

* fix: hcaptcha styles

* fix: copy

* remove: auth/welcome page as its no longer used

* refactor: bring root page card styles into individual components and use tailwind

* fix: account settings modals to use new modal and fix lots of bad styles

* refactor: pnpm prepr

* feat: implement last signed in indicator

* fix: append number when generated name from email is taken

* refactor: pnpm prepr

* fix: last sign in badge color

* fix: qa issues

* refactor: pnpm prepr

* fix: hover effect on native date picker

* chore: temp staging undo

* Revert "chore: temp staging undo"

This reverts commit cad6bd4f92.

* feat: handle app create account

* fix: last signed in style

* fix: add initOnMounted for SSR race

* refactor: use typescript

* refactor: pnpm prepr

* refactor: use typescript for reset-password

* refactor: convert verify-email to use typescript

* refactor: convert authorize.vue to use typescript

* fix: authorize.vue error states

* feat: small style updates

* feat: implement date picker component

* feat: improve UX and styles for range select

* refactor: pnpm prepr

* fix: range select border styles

* feat: implement date picker component in create account

* feat: implement preserve date for date picker

* update rust toolchain

* increase recursion limit

* fix: date picker can be null

* fix: calculate age based on user's timezone

* fix: number input icons color

* fix: date picker icons

* feat: improve styles

* fix: add width on date

* fix: hover color bad on number input

* fix lints

* feat: add default date open view

* fmt

* fix: account.vue

* fix: remove default date to open 13 years ago

* fix: edit copy on info banner

* fix: cannot hover over project card tooltip items (#6071)

fix: cannot hover over project cards

* feat: improve add dependency flow (#6075)

* fix: shadow on nav

* feat: improve add dependency flow

* feat: update suggested dependency style

* feat: update dependency rows to use version number and update styles

* feat: implement combobox select searched text on focus

* feat: add Tabs.vue

* feat: update nav tabs to use tabs

* feat: improve project search dropdown

* fix: dependency search not clearing inbound query

* fix: combobox no options open state bug

* feat: improve dependency project and version search

* fix: open modrinth project links in the app (#6072)

* pin tanstack versions + set pnpm min age to 7 days

* squash commits

* fix: 2 factor auth enter code screen styles

* update copy

* update copy

* improve reset password

* feat: update sign in screen

* fix: unused import

* Merge branch 'main' into boris/dev-908-backend-changes

* Revert "Merge branch 'main' into boris/dev-908-backend-changes"

This reverts commit b9b03796e3.

* fix: add stroke

* feat: add passkey support (#6375)

* feat: add passkey backend

* feat: passkey frontend

* invalidate sessions on compromised passkey

* chore: run sqlx prepare

* fix: make passkey button use both collumns to prevent empty space

* fix: correctly verify max passkeys in finish route

* fix: use structs for response

* fix: add rp name default

* style: use web::Json

* fmt

* feat: improve manage passkeys UI

* fix copy

* pnpm prepr

---------

Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
Co-authored-by: Michael H. <michael@iptables.sh>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com>
2026-06-25 20:36:19 +00:00
aecsocketandGitHub 6fc741f7c0 fix: file scan fix v2 (#6504)
* fix: file attribution scanning not marking completed files

* prepare

* Handle file scan failures gracefully with tracking

* conditional

* add more logging

* remove leftover files
2026-06-25 20:50:04 +02:00
aecsocketandGitHub 527757d158 fix: file attribution scanning not marking completed files (#6500)
* fix: file attribution scanning not marking completed files

* Clean up unused attribution files/groups on deletion

* prepare

* Handle file scan failures gracefully with tracking
2026-06-25 15:57:51 +00:00
aecsocketandGitHub f4a2999b20 Batch pending file scanning ops (#6491)
* Batch pending file scanning ops

* prepare

* move some stuff out of the txn

* scan concurrency

* prepr

* 🦀 automod attribution is dead, long live automod

* fix ci

* update scan file filtering
2026-06-24 21:46:07 +00:00
aecsocketandGitHub 586cdc90f9 fix: word separators in Typesense search fields regression (#6492) 2026-06-24 18:09:18 +00:00
Truman GaoandGitHub e56f3ae18a fix: package datapack as mod (#6494)
* feat: update package as mod copy

* fix: add environment field for v3

* pnpm prepr

* feat: use coloured tags for loaders in multiselect
2026-06-24 18:08:33 +00:00
aecsocketandGitHub c392c85917 perf: Attribution route perf improvements (#6489)
* Attribution route perf improvements

* fix clippy

* clear exemptions when user attempts to scan their file

* Increase Redis TTL/refresh time
2026-06-24 17:48:35 +02:00
Prospector 29110890e1 changelog 2026-06-24 00:22:41 -07:00
Prospector a18f67bf0c Merge branch 'main' into prospector/modpack-proj-fixes 2026-06-24 00:21:19 -07:00
Prospector 623e01dbff bunch more fixes lol 2026-06-24 00:20:02 -07:00
Prospector 14c728c8e3 Copy ID button 2026-06-23 23:03:16 -07:00
ThatGravyBoatandGitHub a1b2073f57 feat: add moderation status action button to tech review (#6484)
* feat: add moderation status action button to tech review

* chore: run prepr
2026-06-24 05:54:39 +00:00
François-Xavier TalbotandGitHub 45c417e406 fix query cache (#6488) 2026-06-24 06:42:14 +02:00
François-Xavier TalbotandGitHub 47c60788ff attributions fixes (#6487)
* feat(labrinth): begin & commit one transaction per file scans

* chore(labrinth): use ro pool for the query
2026-06-24 06:19:10 +02:00
Prospector 3c60cc6f08 project page gap 2026-06-23 15:20:19 -07:00
Michael H. d1e8a9c574 perf: split attribution_enforced_versions subquery to use indexes
The original view used a single correlated `not exists` with an `or`,
which prevented the partial unique indexes on `attributions_exemptions`
from being used. Splitting into two `not exists` clauses lets each
anti-join use its own index instead of a full sequential scan.
2026-06-24 00:19:16 +02:00
aecsocketandGitHub 7bf85239de Improve error handling in account balance notifications (#6478)
* Improve error handling in account balance notifications

* fix compile
2026-06-23 21:11:02 +00:00
Prospector 06353bbc14 move deps after compat 2026-06-23 13:50:35 -07:00
6380dd16d5 chore: modpack permissions blog (#6474)
* chore: add blog

* update bullets

* Update blog

* remove old files

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-06-23 20:24:38 +00:00
Prospector 2376d571bb changelog 2026-06-23 12:31:41 -07:00
e7926083fb feat: new modpack permissions system (#6005)
* Begin external projects moderator database frontend

* add copy link button

* begin project page permissions settings

* MEL database backend routes

* include filename in external files

* wip: when uploading a version file, fetch its overrides as a list

* wip: override license checks

* improve FileHost ref counting

* file host read capability

* scan files when inserting version file

* add dependency sha1 field

* clean up version files

* wip: attributions

* update s3 file host

* attribution scanning basic works

* works

* insert attribution info after resolving

* add routes

* remove dep sha1 stuff

* prepr

* wip: override file sources

* add files_missing_attributions to versions

* return extended version info + attributed at/by

* hook up frontend to backend (mostly)

* expose version date published

* withholding version visibility

* frontend work

* prepr

* use api-client for img upload

* moar frontend

* prepr

* Add schema to attribution resolution and Flame project results

* sqlx prepare

* changes

* remove feature flag, fix optional proof images

* fix schema

* fmt

* fix deletion and file fetch

* prepare

* fix admonition

* update frontend stuff to new schema

* prepr

* attribution on dependencies

* fixes

* sqlx prepare

* fixes

* routes

* fix routes

* Version grandfathering

* prepare

* wip: bulk routes

* pushing what i've got rn

* include link in NoPermission

* change hash insert to bulk route

* query flame even if entry in MEL

* delete file with weird name

* Prioritise putting override files in existing groups even with ExternalLicense

* fix how hex bytes are handled in route

* feat: coolbot moderation changes (#6215)

* Update moderator checklist

* move permissions stage order

* Updated nagContext.versions to v3, added nag for permissions

* Update permissions.vue default messages

* prepr

---------

Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>

* QA

* prepr

* should group by project

* return attribution resolution correctly

* updated by moderator info

* Track what moderator reviewed an attribution moderation status

* default deser FMA field

* new version page

* clean up fetching + add a couple missing features

* qa items

* prepr

* provide moderation package stuff with DI

* format?

* don't redact moderated_at

* move supplementary resources

* Reorganize moderation messages.

* Quick replies for external content permissions.

* prepare

* QA

* allow exempting projects

* Ignore Flame projects which 404

* fix ci

* fix cross project attribution stuff

* Fix permission error

* change what files get cscanned

* add more logging

* QA Jun 22

* fix

* idempotency

* Expose route for rescanning

* update blog link

---------

Co-authored-by: aecsocket <aecsocket@tutanota.com>
Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>
Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
2026-06-23 21:27:51 +02:00
Michael H. a686a93858 fix: preview deployment incorrect branch data 2026-06-23 20:57:47 +02:00
Modrinth BotandGitHub 2876890fe5 New translations from Crowdin (main) (#6466) 2026-06-23 18:36:07 +00:00
Calum H.andGitHub 4c869d2a37 fix: broken icu script (#6480) 2026-06-23 14:43:12 +00:00
ThatGravyBoatandGitHub 525f60c389 fix(docs): missing environment field from v2 createVersion route (#6476)
* fix(docs): missing environment field from v2 createVersion route

* fix(docs): missing environment field from v2 createVersion route
2026-06-23 00:35:41 +00:00
Truman GaoandGitHub 042a27a17c fix: small breakdowns fixes (#6473)
* fix: members to member, no dependents to none

* feat: add fuzzy search to multiselect options search and projects dropdown

* fix: duplicate key
2026-06-22 20:40:53 +00:00
Prospector a747c2f290 changelog 2026-06-22 11:15:40 -07:00
Truman GaoandGitHub d756e5ab93 fix: homepage search relying on generated state (#6471) 2026-06-22 18:09:51 +00:00
Truman GaoandGitHub 1f8b1bd2bb feat: implement dependent projects + members breakdown/filter (#6395)
* feat: implement dependent project breakdown

* implement dependent project type filter

* feat: show dependent on column if there are multiple projects selected

* feat: different tooltip for when both project version and dependent breakdowns are on

* feat: implement dependents query filter

* feat: add project icon to query filter

* fix: dont show dependent on column when project breakdown is selected

* feat: add org icon

* remove: dependent on project column and project version project column

* refactor: pnpm prepr

* fix: all project selection gets unselected when reload page

* pnpm prepr

* fix: still using old search formatter

* feat: implement projects in table click to link to project page

* feat: handle analytics values that dont have a dependent project as a "No dependent" row

* pnpm prepr

* feat: remove org icon for dependents projects and change unknown label

* feat: do not include unknown rows for showing top 8

* feat: separate out no dependents based on download reason

* fix: use compatible dependencies for query filter options

* feat: implement members breakdown/filter frontend (#6470)

* feat: add link to user in table

* fix: do not allow breakdowns to occur without shared stats

* pnpm prepr

* fix: i18n and passing params for formatter
2026-06-22 18:09:00 +00:00
aecsocketandGitHub 23edb2351d Filter servers out from analytics dependents (#6445) 2026-06-22 15:07:02 +00:00
2c53e1240e feat: add analytics fetch users mapping (#6439)
* feat: add users mapping

* remove skip_serialize_if

---------

Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
2026-06-22 15:06:12 +00:00
aecsocketandGitHub b7d097d74d Batch incremental search event consumption (#6468)
* Batch incremental search event consumption

* fmt

* fix
2026-06-22 14:54:02 +00:00
aecsocketandGitHub 0a0ef1ce81 Fix account deletion deleting incorrect report threads (#6465) 2026-06-22 14:27:31 +00:00
d7917048d5 fix: allow PAT scope labels to wrap on long strings (#6456)
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
2026-06-21 23:03:11 +00:00
Emma F.andGitHub a0017cf58f fix: nested details tags in markdown bodies (#6458) 2026-06-21 18:56:24 +00:00
SychicandGitHub b188bbf617 fix(maven): return requested file extension (#6455)
* fix(maven): return requested file extension

closes #2428, #2663
why was this hard coded lol

* fix(maven): handle other file types properly
2026-06-21 13:09:14 +00:00
Michael H.andGitHub 79ce1adf9d chore: kebab-case topic name 2026-06-21 14:42:24 +02:00
aecsocketandGitHub c6ef10e798 Incremental search indexing (#6453)
* wip: initial kafka support

* wip: kafka search queue

* wip: kafka consumer

* wip: create/delete docs

* reindexing works

* reindex in more places

* also incrementally update server pings

* cleanup

* tombi

* fix ci

* update tombi

* prepare

* fix ci

* fix

* fix PLEASE

* 🙏

* add route to force reindex project
2026-06-21 10:03:20 +00:00
aecsocketandGitHub be65589e4d Allow v2 version creation route to accept environment (#6454)
* Allow v2 version creation route to accept environment

* fix
2026-06-20 19:30:32 +00:00
ThatGravyBoatandGitHub 86a224419c fix: users projects shifting around while in queue (#6452)
fix: users projects shifting around while in queue due to download count changes
2026-06-20 15:55:03 +00:00
Calum H.andGitHub 49dd844fe1 feat: changelog app 0.14.8 (#6450) 2026-06-19 20:34:28 +00:00
Calum H.andGitHub 56536abb24 fix: modal shift on close + readd settings preload (#6448)
* fix: NewModal layout shift on close

* fix: preload queries in settings modals

* fix: any
2026-06-19 20:23:35 +00:00
aecsocketandGitHub ac5daad280 Tiltify API query backoff (#6447)
* Tiltify API querying backoff

* fix backoff

* fix clippy

* fix2
2026-06-19 19:02:22 +00:00
checkrdandGitHub 87eec7741b fix: remove max-width cap on floating action bar for mobile screens (#6430) 2026-06-19 18:38:33 +00:00
Modrinth BotandGitHub 4c9ee5b1c4 New translations from Crowdin (main) (#6403) 2026-06-19 18:08:03 +00:00
Truman GaoandGitHub caeab46ca6 fix: clicking scrollbar closes combobox (#6443)
* fix: clicking overlay scrollbar closes combobox

* feat: adjust multiselect scrollbar style

* feat: for dropdown mutliselect too
2026-06-19 18:07:13 +00:00
Calum H.andGitHub 8e6004fdd5 fix: billing fix for medal servers (#6437)
* fix: billing fix for medal servers

* fix: lint
2026-06-19 18:04:25 +00:00
aecsocketandGitHub 50b2b9567c Fix token separators in game version search (#6435)
* Fix token separators in game version search

* fix
2026-06-19 15:50:02 +00:00
Truman GaoandGitHub e0ea14226e feat: add dawn launcher user agent (#6441)
* feat: add dawn launcher user agent

* rename to Dawn
2026-06-19 14:31:47 +00:00
Calum H.andGitHub d2b85c9f8e fix: pre-migration for skins drag and drop change (#6402) 2026-06-19 14:16:51 +00:00
aecsocketandGitHub d33f00d2b1 Tighten URL slug validation (#6442)
* Tighten URL slug validation

* slug sanitization in frontend
2026-06-19 13:52:24 +00:00
aecsocketandGitHub b3257a0614 Allow revenue analytics to bucket by user ID (#6428)
* adjust

* convert to sqlx macros

* Allow users on a team to see rev splits of other users

* prepare

* clarify comment
2026-06-18 12:12:56 +00:00
François-Xavier TalbotandGitHub 486b467af2 cont: fix(frontend): dont assume wss for panel pingtest as well (#6421)
* fix(frontend): dont assume wss for panel pingtest as well

* fix(frontend): wss assumptions

* chore: fix export

* chore: prettier
2026-06-17 22:54:09 +00:00
aecsocketandGitHub 336050f4df Clear owned projects cache when deleting an org with projects (#6429) 2026-06-17 19:24:44 +00:00
aecsocketandGitHub 2bb1ef775c Serve stale Tiltify data if API is not available (#6427) 2026-06-17 14:58:07 +00:00
François-Xavier TalbotandGitHub 5ed322d281 feat(frontend): don't assume secure protocol for kyros websocket/fs (#6420)
* feat(frontend): don't assume https as websocket/fs protocol

* fix(frontend): actually do the same for websocket

* fix(frontend): don't strip ws path
2026-06-16 21:39:38 +00:00
Calum H.andGitHub 8591bc8ebc feat: changelog app 0.14.7 (#6419) 2026-06-16 19:05:19 +00:00
01fd18d550 Fix: update modpack export filter (#6359)
* Update Modpack export filters

* fix: move file export filtering from vue to rust

* chore: remove unused import

---------

Co-authored-by: Gravy Boat <gravy@thatgravyboat.tech>
2026-06-16 18:51:45 +00:00
François-Xavier TalbotandGitHub 3aaa2ef071 feat(app-backend): key circuit breaker by URI path pattern (#6383)
* feat(app-lib): split `FetchFence` on base URI path

* chore: fmt

* fix(app-lib): fix test timing
2026-06-16 18:49:06 +00:00
Truman GaoandGitHub a3aeeac2c3 fix: babric mods incorrectly detected as fabric (#6407)
* fix: babric mods incorrectly detected as fabric

* remove: standard version, just check for fabric normalized beta version number

* add comment
2026-06-16 18:47:16 +00:00
Calum H.andGitHub dfba212be4 fix: update checking version match reliability (#6411) 2026-06-16 18:45:31 +00:00
Calum H.andGitHub 3877999e53 fix: update browse page when installing dependencies (#6414)
* fix: update browse page when installing dependencies

* fix: lint

* fix: vers page

* fix: vers page link query persistence
2026-06-16 18:36:42 +00:00
Calum H.andGitHub 46a7cf490d feat: implement dependency deletion modal for content tab (#6410)
* feat: implement dependency deletion modal for content tab

* fix: handle bulk delete state

* fix: lint
2026-06-16 18:36:30 +00:00
Truman GaoandGitHub b5f7406998 fix: game version filter in search using quotes instead of backticks (#6415) 2026-06-16 18:22:52 +00:00
ThatGravyBoatandGitHub 3679d2c786 fix: maven version ranges not matching correctly (#6416) 2026-06-16 17:28:50 +00:00
1182 changed files with 96747 additions and 24532 deletions
@@ -81,6 +81,6 @@ Please follow these rules precisely:
Use existing patterns from our codebase:
- Variables/plurals: see `apps/frontend/src/pages/frog.vue`
- Rich-text link tags: see `apps/frontend/src/pages/auth/welcome.vue` and `apps/frontend/src/error.vue`
- Rich-text link tags: see `apps/frontend/src/error.vue`
When you finish, there should be no hard-coded English strings left in the template—everything comes from `formatMessage` or `<IntlFormatted>`.
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: taiki-e/install-action@b5fddbb5361bce8a06fb168c9d403a6cc552b084 # v2.75.29
- uses: taiki-e/install-action@fd2f5e3d644b484055ebf4268f474c565f148f25 # v2.81.9
with:
tool: tombi
- run: tombi lint
+3 -7
View File
@@ -76,13 +76,6 @@ jobs:
env:
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
SCCACHE_MULTILEVEL_CHAIN: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'disk,s3' || '' }}
SCCACHE_S3_KEY_PREFIX: ${{ needs.skip-if-clean.outputs.internal == 'true' && format('{0}/', github.repository) || '' }}
SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }}
SCCACHE_REGION: ${{ secrets.SCCACHE_REGION }}
SCCACHE_ENDPOINT: ${{ secrets.SCCACHE_ENDPOINT }}
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }}
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
needs: [skip-if-clean]
if: ${{ needs.skip-if-clean.outputs.skip != 'true' }}
@@ -96,6 +89,9 @@ jobs:
rustflags: ''
cache: false
- name: Setup mold
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Cache Cargo registry and index
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
+15 -5
View File
@@ -20,6 +20,14 @@ on:
required: true
type: string
description: 'The environment to deploy to (staging-preview or production-preview)'
head_sha:
required: false
type: string
description: 'The commit SHA to deploy (defaults to github.sha)'
head_ref:
required: false
type: string
description: 'The branch name to deploy (defaults to github.ref_name)'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.environment || 'push' }}
@@ -40,12 +48,14 @@ jobs:
- name: Configure environment
id: meta
env:
ENV_INPUT: ${{ inputs.environment }}
REF: ${{ github.ref }}
DEPLOY_SHA: ${{ inputs.head_sha || github.sha }}
run: |
echo "cmd=deploy" >> $GITHUB_OUTPUT
ENV_INPUT="${{ inputs.environment }}"
REF="${{ github.ref }}"
SHA_SHORT="${GITHUB_SHA::8}"
SHA_SHORT="${DEPLOY_SHA::8}"
if [ "$ENV_INPUT" = "staging-preview" ]; then
echo "env=staging" >> $GITHUB_OUTPUT
@@ -106,8 +116,8 @@ jobs:
working-directory: ./apps/frontend
run: pnpm build
env:
CF_PAGES_BRANCH: ${{ github.ref_name }}
CF_PAGES_COMMIT_SHA: ${{ github.sha }}
CF_PAGES_BRANCH: ${{ inputs.head_ref || github.ref_name }}
CF_PAGES_COMMIT_SHA: ${{ inputs.head_sha || github.sha }}
CF_PAGES_URL: ${{ steps.meta.outputs.url }}
BUILD_ENV: ${{ steps.meta.outputs.env }}
PREVIEW: ${{ inputs.environment != '' && 'true' || 'false' }}
+9 -3
View File
@@ -24,6 +24,8 @@ jobs:
environment: [staging-preview, production-preview]
with:
environment: ${{ matrix.environment }}
head_sha: ${{ github.event.pull_request.head.sha }}
head_ref: ${{ github.event.pull_request.head.ref }}
deploy-storybook:
if: github.repository_owner == 'modrinth' && github.event.pull_request.head.repo.full_name == github.repository
@@ -68,7 +70,9 @@ jobs:
- name: Configure short SHA
id: meta
run: echo "sha_short=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: echo "sha_short=${HEAD_SHA::8}" >> $GITHUB_OUTPUT
- name: Deploy Storybook preview
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3.15.0
@@ -93,6 +97,8 @@ jobs:
- name: Read deployment URLs
id: urls
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
STAGING_PREVIEW_URL=$(cat deployment-url-staging-preview.txt)
PRODUCTION_PREVIEW_URL=$(cat deployment-url-production-preview.txt)
@@ -102,7 +108,7 @@ jobs:
echo "staging-preview-url=$STAGING_PREVIEW_URL" >> $GITHUB_OUTPUT
echo "production-preview-url=$PRODUCTION_PREVIEW_URL" >> $GITHUB_OUTPUT
echo "storybook-preview-url=https://git-${GITHUB_SHA::8}-storybook.modrinth.workers.dev" >> $GITHUB_OUTPUT
echo "storybook-preview-url=https://git-${HEAD_SHA::8}-storybook.modrinth.workers.dev" >> $GITHUB_OUTPUT
- name: Find comment
if: github.event_name == 'pull_request'
@@ -123,7 +129,7 @@ jobs:
body: |
## Frontend previews
Last deployed commit is [${{ github.sha }}](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.sha }})
Last deployed commit is [${{ github.event.pull_request.head.sha }}](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.event.pull_request.head.sha }})
| Environment | URL |
|-------------|-----|
+8 -7
View File
@@ -78,14 +78,8 @@ jobs:
GIT_HASH: ${{ github.sha }}
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
SCCACHE_MULTILEVEL_CHAIN: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'disk,s3' || '' }}
SCCACHE_S3_KEY_PREFIX: ${{ needs.skip-if-clean.outputs.internal == 'true' && format('{0}/', github.repository) || '' }}
SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }}
SCCACHE_REGION: ${{ secrets.SCCACHE_REGION }}
SCCACHE_ENDPOINT: ${{ secrets.SCCACHE_ENDPOINT }}
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }}
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
RUST_MIN_STACK: '16777216'
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -99,6 +93,13 @@ jobs:
- name: Setup mold
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake libcurl4-openssl-dev
#uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
#with:
# packages: cmake libcurl4-openssl-dev
# version: v2 # cache key
- name: Cache Cargo registry and index
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
+4
View File
@@ -84,6 +84,10 @@ jobs:
rustflags: ''
target: ${{ contains(matrix.platform, 'macos') && 'x86_64-apple-darwin' || '' }}
- name: Setup mold
if: contains(matrix.platform, 'ubuntu')
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Setup sccache
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
+6 -10
View File
@@ -81,13 +81,6 @@ jobs:
# blacksmith runner)
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
SCCACHE_MULTILEVEL_CHAIN: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'disk,s3' || '' }}
SCCACHE_S3_KEY_PREFIX: ${{ needs.skip-if-clean.outputs.internal == 'true' && format('{0}/', github.repository) || '' }}
SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }}
SCCACHE_REGION: ${{ secrets.SCCACHE_REGION }}
SCCACHE_ENDPOINT: ${{ secrets.SCCACHE_ENDPOINT }}
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }}
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
steps:
@@ -99,8 +92,8 @@ jobs:
- name: Install build dependencies
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
with:
packages: libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev
version: v1 # cache key
packages: cmake libcurl4-openssl-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev
version: v2 # cache key
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -129,6 +122,9 @@ jobs:
components: clippy, rustfmt
cache: false
- name: Setup mold
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 / Mold 2.41.0
- name: Cache Cargo registry and index
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae #v5.0.5
with:
@@ -153,7 +149,7 @@ jobs:
uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 # v1.18.1
- name: Setup nextest
run: cargo binstall --no-confirm --secure cargo-nextest@0.9.133
run: cargo binstall --no-confirm --secure --locked cargo-nextest@0.9.133
# cargo-binstall does not have pre-built binaries for sqlx-cli, so we fall
# back to a cached cargo install
+1 -6
View File
@@ -1,12 +1,7 @@
{
"prettier.endOfLine": "lf",
"editor.formatOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"files.eol": "\n",
+2 -2
View File
@@ -50,7 +50,7 @@ Run these from the **root** folder before opening a pull request - do not run th
- **App frontend:** `pnpm prepr:frontend:app`
- **Frontend libs:** `pnpm prepr:frontend:lib`
- **All frontend (app+web):** `pnpm prepr`
- **Labrinth (backend):** See `apps/labrinth/CLAUDE.md`
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
The website and app `prepr` commands
@@ -62,7 +62,7 @@ The website and app `prepr` commands
## Project-Specific Instructions
Each project may have its own `CLAUDE.md` with detailed instructions:
Each project may have its own file with detailed instructions:
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
- [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) - Frontend Website
Generated
+660 -109
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -9,6 +9,7 @@ members = [
"packages/ariadne",
"packages/daedalus",
"packages/labrinth-derive",
"packages/modrinth-content-management",
"packages/modrinth-log",
"packages/modrinth-maxmind",
"packages/modrinth-util",
@@ -32,7 +33,6 @@ actix-ws = "0.3.0"
arc-swap = "1.7.1"
argon2 = { version = "0.5.3", features = ["std"] }
ariadne = { path = "packages/ariadne" }
async-compression = { version = "0.4.32", default-features = false }
async-minecraft-ping = { path = "packages/async-minecraft-ping" }
async-recursion = "1.1.1"
async-stripe = { version = "0.41.0", default-features = false, features = [
@@ -51,6 +51,7 @@ aws-sdk-s3 = { version = "=1.122.0", default-features = false, features = [
] }
base64 = "0.22.1"
bitflags = "2.9.4"
bon = "3.9.3"
bytemuck = "1.24.0"
bytes = "1.10.1"
censor = "0.3.0"
@@ -119,6 +120,7 @@ lettre = { version = "0.11.19", default-features = false, features = [
] }
maxminddb = "0.26.0"
meilisearch-sdk = { version = "0.30.0", default-features = false }
modrinth-content-management = { path = "packages/modrinth-content-management" }
modrinth-log = { path = "packages/modrinth-log" }
modrinth-util = { path = "packages/modrinth-util" }
muralpay = { path = "packages/muralpay" }
@@ -140,6 +142,7 @@ quick-xml = "0.38.3"
quote = { version = "1.0" }
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
rdkafka = { version = "0.36.2", features = ["cmake-build"] }
redis = "0.32.7"
regex = "1.12.2"
reqwest = { version = "0.12.24", default-features = false }
@@ -156,6 +159,7 @@ rust-s3 = { version = "0.37.0", default-features = false, features = [
] }
rustls = "0.23.32"
rusty-money = "0.4.1"
scalar_api_reference = { version = "0.2.2", default-features = false }
secrecy = "0.10.3"
sentry = { version = "0.45.0", default-features = false, features = [
"backtrace",
@@ -215,10 +219,10 @@ typed-path = "0.12.0"
url = "2.5.7"
urlencoding = "2.1.3"
utoipa = { version = "5.4.0", features = ["actix_extras", "chrono", "decimal"] }
utoipa-actix-web = { version = "0.1.2" }
utoipa-scalar = { version = "0.3.0", default-features = false }
uuid = "1.18.1"
validator = "0.20.0"
webauthn-rs = "0.5.5"
webauthn-rs-proto = "0.5.5"
webp = { version = "0.3.1", default-features = false }
webview2-com = "0.38.0" # Should be updated in lockstep with wry
whoami = "1.6.1"
@@ -269,6 +273,7 @@ redundant_type_annotations = "warn"
result_large_err = "allow"
todo = "warn"
too_many_arguments = "allow"
type_complexity = "allow"
uninlined_format_args = "warn"
unnested_or_patterns = "warn"
wildcard_dependencies = "warn"
+2 -2
View File
@@ -3,13 +3,13 @@
![Issues](https://img.shields.io/github/issues-raw/Modrinth/code?color=c78aff&label=issues&style=for-the-badge)
![Pull Requests](https://img.shields.io/github/issues-pr-raw/Modrinth/code?color=c78aff&label=PRs&style=for-the-badge)
![Contributors](https://img.shields.io/github/contributors/Modrinth/code?color=c78aff&label=contributors&style=for-the-badge)
![Lines](https://img.shields.io/endpoint?url=https://ghloc.vercel.app/api/modrinth/code/badge?style=flat&logoColor=white&color=c78aff&style=for-the-badge)
![Lines of Code](https://img.shields.io/endpoint?url=https://loctopus.creeperkatze.dev/github/modrinth/code/badge?style=flat&logoColor=white&color=c78aff&style=for-the-badge)
![Commit Activity](https://img.shields.io/github/commit-activity/m/Modrinth/code?color=c78aff&label=commits&style=for-the-badge)
![Last Commit](https://img.shields.io/github/last-commit/Modrinth/code?color=c78aff&label=last%20commit&style=for-the-badge)
## Modrinth Monorepo
Welcome to the Modrinth Monorepo, the primary codebase for the Modrinth web interface and app. It contains ![Lines](https://img.shields.io/endpoint?url=https://ghloc.vercel.app/api/modrinth/olympus/badge?logoColor=white&color=black&label=) lines of code and has ![Contributors](https://img.shields.io/github/contributors/Modrinth/code?color=black&label=) contributors!
Welcome to the Modrinth Monorepo, the primary codebase for the Modrinth web interface and app. It contains ![Lines of Code](https://img.shields.io/endpoint?url=https://loctopus.creeperkatze.dev/github/modrinth/code/badge%3Fformat%3Dhuman&logoColor=white&color=black&label=) lines of code and has ![Contributors](https://img.shields.io/github/contributors/Modrinth/code?color=black&label=) contributors!
If you're not a developer and you've stumbled upon this repository, you can access the web interface on the [Modrinth website](https://modrinth.com) and download the latest release of the app [here](https://modrinth.com/app).
+28 -11
View File
@@ -94,9 +94,9 @@ import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
import { get_user, get_version } from '@/helpers/cache.js'
import { command_listener, notification_listener, warning_listener } from '@/helpers/events.js'
import { install_create_modpack_instance, install_get_modpack_preview } from '@/helpers/install'
import { list, run } from '@/helpers/instance'
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
import { create_profile_and_install_from_file } from '@/helpers/pack'
import { list } from '@/helpers/profile.js'
import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
import { get_opening_command, initialize_state } from '@/helpers/state'
@@ -110,6 +110,7 @@ import {
isNetworkMetered,
setRestartAfterPendingUpdate,
} from '@/helpers/utils.js'
import { start_join_server, start_join_singleplayer_world } from '@/helpers/worlds.ts'
import i18n from '@/i18n.config'
import {
appUpdateState,
@@ -852,13 +853,30 @@ async function handleCommand(e) {
if (e.event === 'RunMRPack') {
// RunMRPack should directly install a local mrpack given a path
if (e.path.endsWith('.mrpack')) {
await create_profile_and_install_from_file(e.path, (createProfile, fileName) =>
unknownPackWarningModal.value?.show(createProfile, fileName),
).catch(handleError)
const location = { type: 'fromFile', path: e.path }
const preview = await install_get_modpack_preview(location).catch(handleError)
if (preview?.unknownFile) {
const splitPath = e.path.split(/[\\/]/)
const fileName = splitPath ? splitPath[splitPath.length - 1] : e.path
unknownPackWarningModal.value?.show(
() => install_create_modpack_instance(location).then(() => undefined),
fileName,
)
} else {
await install_create_modpack_instance(location).catch(handleError)
}
trackEvent('InstanceCreate', {
source: 'CreationModalFileDrop',
})
}
} else if (e.event === 'LaunchInstance') {
if (e.server) {
await start_join_server(e.id, e.server).catch(handleError)
} else if (e.singleplayer_world) {
await start_join_singleplayer_world(e.id, e.singleplayer_world).catch(handleError)
} else {
await run(e.id).catch(handleError)
}
} else if (e.event === 'InstallServer') {
await router.push(`/project/${e.id}`)
await playServerProject(e.id).catch(handleError)
@@ -1323,12 +1341,11 @@ async function processPendingSurveys() {
const creds = await getCreds().catch(handleError)
const userId = creds?.user_id
const instances = await list().catch(handleError)
const isActivePlayer =
instances.findIndex(
(instance) =>
isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created),
) >= 0
const instances = (await list().catch(handleError)) ?? []
const isActivePlayer = instances.some(
(instance) =>
isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created),
)
let surveys = []
try {
@@ -24,7 +24,8 @@ import { computed, ref } from 'vue'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import Instance from '@/components/ui/Instance.vue'
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
import { duplicate, remove } from '@/helpers/profile.js'
import { install_duplicate_instance } from '@/helpers/install'
import { remove } from '@/helpers/instance'
const { handleError } = injectNotificationManager()
@@ -48,21 +49,21 @@ const instanceComponents = ref(null)
const currentDeleteInstance = ref(null)
const confirmModal = ref(null)
async function deleteProfile() {
async function deleteInstance() {
if (currentDeleteInstance.value) {
instanceComponents.value = instanceComponents.value.filter(
(x) => x.instance.path !== currentDeleteInstance.value,
(x) => x.instance.id !== currentDeleteInstance.value,
)
await remove(currentDeleteInstance.value).catch(handleError)
}
}
async function duplicateProfile(p) {
await duplicate(p).catch(handleError)
async function duplicateInstance(p) {
await install_duplicate_instance(p).catch(handleError)
}
const handleRightClick = (event, profilePathId) => {
const item = instanceComponents.value.find((x) => x.instance.path === profilePathId)
const handleRightClick = (event, instanceId) => {
const item = instanceComponents.value.find((x) => x.instance.id === instanceId)
const baseOptions = [
{ name: 'add_content' },
{ type: 'divider' },
@@ -114,16 +115,16 @@ const handleOptionsClick = async (args) => {
break
case 'duplicate':
if (args.item.instance.install_stage == 'installed')
await duplicateProfile(args.item.instance.path)
await duplicateInstance(args.item.instance.id)
break
case 'open':
await args.item.openFolder()
break
case 'copy':
await navigator.clipboard.writeText(args.item.instance.path)
await navigator.clipboard.writeText(args.item.instance.id)
break
case 'delete':
currentDeleteInstance.value = args.item.instance.path
currentDeleteInstance.value = args.item.instance.id
confirmModal.value.show()
break
}
@@ -321,13 +322,13 @@ const filteredResults = computed(() => {
<Instance
v-for="instance in instanceSection.value"
ref="instanceComponents"
:key="instance.path + instance.install_stage"
:key="instance.id + instance.install_stage"
:instance="instance"
@contextmenu.prevent.stop="(event) => handleRightClick(event, instance.path)"
@contextmenu.prevent.stop="(event) => handleRightClick(event, instance.id)"
/>
</section>
</Accordion>
<ConfirmDeleteInstanceModal ref="confirmModal" @delete="deleteProfile" />
<ConfirmDeleteInstanceModal ref="confirmModal" @delete="deleteInstance" />
<ContextMenu ref="instanceOptions" @option-clicked="handleOptionsClick">
<template #play> <PlayIcon /> Play </template>
<template #stop> <StopCircleIcon /> Stop </template>
+18 -19
View File
@@ -21,9 +21,10 @@ import Instance from '@/components/ui/Instance.vue'
import LegacyProjectCard from '@/components/ui/LegacyProjectCard.vue'
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_by_profile_path } from '@/helpers/process.js'
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import { install_duplicate_instance } from '@/helpers/install'
import { kill, remove, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process.js'
import { showInstanceInFolder } from '@/helpers/utils.js'
import { injectContentInstall } from '@/providers/content-install'
import { handleSevereError } from '@/store/error.js'
@@ -60,14 +61,14 @@ const deleteConfirmModal = ref(null)
const currentDeleteInstance = ref(null)
async function deleteProfile() {
async function deleteInstance() {
if (currentDeleteInstance.value) {
await remove(currentDeleteInstance.value).catch(handleError)
}
}
async function duplicateProfile(p) {
await duplicate(p).catch(handleError)
async function duplicateInstance(p) {
await install_duplicate_instance(p).catch(handleError)
}
const handleInstanceRightClick = async (event, passedInstance) => {
@@ -85,7 +86,7 @@ const handleInstanceRightClick = async (event, passedInstance) => {
},
]
const runningProcesses = await get_by_profile_path(passedInstance.path).catch(handleError)
const runningProcesses = await get_by_instance_id(passedInstance.id).catch(handleError)
const options =
runningProcesses.length > 0
@@ -126,16 +127,14 @@ const handleProjectClick = (event, passedInstance) => {
const handleOptionsClick = async (args) => {
switch (args.option) {
case 'play':
await run(args.item.path).catch((err) =>
handleSevereError(err, { profilePath: args.item.path }),
)
await run(args.item.id).catch((err) => handleSevereError(err, { instanceId: args.item.id }))
trackEvent('InstanceStart', {
loader: args.item.loader,
game_version: args.item.game_version,
})
break
case 'stop':
await kill(args.item.path).catch(handleError)
await kill(args.item.id).catch(handleError)
trackEvent('InstanceStop', {
loader: args.item.loader,
game_version: args.item.game_version,
@@ -144,26 +143,26 @@ const handleOptionsClick = async (args) => {
case 'add_content':
await router.push({
path: `/browse/${args.item.loader === 'vanilla' ? 'datapack' : 'mod'}`,
query: { i: args.item.path },
query: { i: args.item.id },
})
break
case 'edit':
await router.push({
path: `/instance/${encodeURIComponent(args.item.path)}`,
path: `/instance/${encodeURIComponent(args.item.id)}`,
})
break
case 'duplicate':
if (args.item.install_stage == 'installed') await duplicateProfile(args.item.path)
if (args.item.install_stage == 'installed') await duplicateInstance(args.item.id)
break
case 'delete':
currentDeleteInstance.value = args.item.path
currentDeleteInstance.value = args.item.id
deleteConfirmModal.value.show()
break
case 'open_folder':
await showProfileInFolder(args.item.path)
await showInstanceInFolder(args.item.id)
break
case 'copy_path':
await navigator.clipboard.writeText(args.item.path)
await navigator.clipboard.writeText(args.item.id)
break
case 'install': {
await installVersion(
@@ -239,7 +238,7 @@ onUnmounted(() => {
</script>
<template>
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="deleteProfile" />
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="deleteInstance" />
<div ref="rowContainer" class="flex flex-col gap-4">
<div v-for="row in actualInstances" ref="rows" :key="row.label" class="row">
<HeadingLink class="mt-1" :to="row.route">
@@ -256,7 +255,7 @@ onUnmounted(() => {
0,
row.compact ? maxInstancesPerCompactRow : maxInstancesPerRow,
)"
:key="row.label + instance.path"
:key="row.label + instance.id"
:instance="instance"
:compact="row.compact"
:first="instanceIndex === 0"
@@ -4,7 +4,7 @@ import { ButtonStyled, injectNotificationManager, OverflowMenu } from '@modrinth
import { open } from '@tauri-apps/plugin-dialog'
import { useRouter } from 'vue-router'
import { add_project_from_path } from '@/helpers/profile.js'
import { add_project_from_path } from '@/helpers/instance'
const { handleError } = injectNotificationManager()
@@ -22,14 +22,14 @@ const handleAddContentFromFile = async () => {
if (!newProject) return
for (const project of newProject) {
await add_project_from_path(props.instance.path, project.path ?? project).catch(handleError)
await add_project_from_path(props.instance.id, project.path ?? project).catch(handleError)
}
}
const handleSearchContent = async () => {
await router.push({
path: `/browse/${props.instance.loader === 'vanilla' ? 'resourcepack' : 'mod'}`,
query: { i: props.instance.path },
query: { i: props.instance.id },
})
}
</script>
@@ -14,31 +14,7 @@
<UnplugIcon class="text-secondary" />
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
</div>
<ButtonStyled color="brand" type="outlined" hover-color-fill="background">
<button
v-if="showUpdatePill"
type="button"
class="!h-[34px] overflow-hidden text-sm !transition-[width,opacity,transform,background-color,color,filter] !duration-200 ease-out"
:class="[
updatePillWidthClass,
{
'update-pill-ready-hidden': finishedDownloading && !animateReadyPill,
'update-pill-ready-visible': finishedDownloading && animateReadyPill,
},
]"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" :class="{ 'animate-spin': restarting }" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">{{ downloadPercent }}%</span>
</span>
<span v-else>{{ updateLabel }}</span>
</button>
</ButtonStyled>
<AppUpdateButton />
<div
class="flex border-solid border-surface-5 text-sm items-center gap-2 py-1.5 px-3 rounded-xl border"
>
@@ -47,28 +23,28 @@
<div class="text-contrast flex items-center gap-2">
<router-link
v-tooltip="formatMessage(messages.viewInstance)"
:to="`/instance/${encodeURIComponent(selectedProcess.profile.path)}`"
:to="`/instance/${encodeURIComponent(selectedProcess.instance.id)}`"
class="hover:underline"
>
{{ selectedProcess.profile.name }}
{{ selectedProcess.instance.name }}
</router-link>
<Dropdown
v-if="currentProcesses.length > 1"
placement="bottom"
:triggers="['click']"
:hide-triggers="['click']"
@show="showProfiles = true"
@hide="showProfiles = false"
@show="showInstances = true"
@hide="showInstances = false"
>
<ButtonStyled type="transparent" circular size="small">
<button
v-tooltip="
showProfiles
showInstances
? formatMessage(messages.hideMoreRunningInstances)
: formatMessage(messages.showMoreRunningInstances)
"
>
<DropdownIcon :class="{ 'rotate-180': !!showProfiles }" />
<DropdownIcon :class="{ 'rotate-180': !!showInstances }" />
</button>
</ButtonStyled>
<template #popper>
@@ -93,7 +69,7 @@
>
<OnlineIndicatorIcon />
<span class="mr-auto text-contrast flex items-center gap-2">
{{ process.profile.name }}
{{ process.instance.name }}
<StarIcon v-if="process.uuid === selectedProcess.uuid" class="text-orange" />
</span>
</button>
@@ -107,7 +83,7 @@
<button
v-tooltip="formatMessage(messages.viewLogs)"
class="active:scale-95 flex"
@click.stop="goToTerminal(process.profile.path)"
@click.stop="goToTerminal(process.instance.id)"
>
<TerminalSquareIcon class="text-secondary size-5" />
</button>
@@ -144,7 +120,6 @@ import {
DownloadIcon,
DropdownIcon,
OnlineIndicatorIcon,
RefreshCwIcon,
StarIcon,
StopCircleIcon,
TerminalSquareIcon,
@@ -161,21 +136,18 @@ import {
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { Dropdown } from 'floating-vue'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import AppUpdateButton from '@/components/ui/app-update-button/index.vue'
import { useInstallJobNotifications } from '@/composables/browse/install-job-notifications'
import { trackEvent } from '@/helpers/analytics'
import { loading_listener, process_listener } from '@/helpers/events'
import { get_many as getInstances } from '@/helpers/instance'
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
import { get_many as getInstances } from '@/helpers/profile.js'
import type { LoadingBar } from '@/helpers/state'
import { progress_bars_list } from '@/helpers/state'
import type { GameInstance } from '@/helpers/types'
import {
appUpdateState,
downloadAvailableAppUpdate,
installAvailableAppUpdate,
} from '@/providers/app-update'
const { handleError } = injectNotificationManager()
const popupNotificationManager = injectPopupNotificationManager()
@@ -183,12 +155,12 @@ const { formatMessage } = useVIntl()
const router = useRouter()
const showProfiles = ref(false)
const showInstances = ref(false)
interface RunningProcess {
uuid: string
profile_path: string
profile: GameInstance
instance_id: string
instance: GameInstance
}
const messages = defineMessages({
@@ -240,96 +212,8 @@ const messages = defineMessages({
id: 'app.action-bar.view-active-downloads',
defaultMessage: 'View active downloads',
},
update: {
id: 'app.action-bar.update',
defaultMessage: 'Update',
},
downloadingUpdate: {
id: 'app.action-bar.downloading-update',
defaultMessage: 'Downloading update',
},
reloadToUpdate: {
id: 'app.action-bar.reload-to-update',
defaultMessage: 'Reload to update',
},
})
const {
downloading,
downloadPercent,
downloadProgress,
finishedDownloading,
isVisible: isUpdateVisible,
metered,
restarting,
} = appUpdateState
const isUpdateDownloading = computed(
() =>
downloading.value ||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
)
const showUpdatePill = computed(
() => isUpdateVisible.value && (finishedDownloading.value || metered.value),
)
const animateReadyPill = ref(false)
const updateLabel = computed(() => {
if (isUpdateDownloading.value) {
return formatMessage(messages.downloadingUpdate)
}
if (finishedDownloading.value) {
return formatMessage(messages.reloadToUpdate)
}
return formatMessage(messages.update)
})
const updatePillWidthClass = computed(() => {
if (isUpdateDownloading.value) {
return 'w-[219px]'
}
if (finishedDownloading.value) {
return 'w-[166px]'
}
return '!w-[96px]'
})
let readyPillAnimationFrame: number | null = null
watch([showUpdatePill, finishedDownloading], async ([show, ready], [wasShown, wasReady]) => {
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
readyPillAnimationFrame = null
}
if (!show || !ready) {
animateReadyPill.value = false
return
}
if (wasShown && wasReady) {
return
}
animateReadyPill.value = false
await nextTick()
readyPillAnimationFrame = requestAnimationFrame(() => {
animateReadyPill.value = true
readyPillAnimationFrame = null
})
})
async function handleUpdateClick() {
if (isUpdateDownloading.value) {
return
}
if (finishedDownloading.value) {
await installAvailableAppUpdate()
} else {
await downloadAvailableAppUpdate()
}
}
const currentProcesses = ref<RunningProcess[]>([])
const selectedProcess = ref<RunningProcess | undefined>()
@@ -337,22 +221,22 @@ const refresh = async () => {
const processes = ((await getRunningProcesses().catch((error) => {
handleError(error)
return []
})) ?? []) as Array<{ uuid: string; profile_path: string }>
const paths = processes.map((process) => process.profile_path)
const profiles: GameInstance[] = await getInstances(paths).catch((error) => {
})) ?? []) as Array<{ uuid: string; instance_id: string }>
const instanceIds = processes.map((process) => process.instance_id)
const instances: GameInstance[] = await getInstances(instanceIds).catch((error) => {
handleError(error)
return []
})
currentProcesses.value = processes
.map((process) => {
const profile = profiles.find((item) => process.profile_path === item.path)
if (!profile) {
const instance = instances.find((item) => process.instance_id === item.id)
if (!instance) {
return null
}
return {
...process,
profile,
instance,
}
})
.filter((process): process is RunningProcess => process !== null)
@@ -385,8 +269,8 @@ const stop = async (process: RunningProcess) => {
await killProcess(process.uuid).catch(handleError)
trackEvent('InstanceStop', {
loader: process.profile.loader,
game_version: process.profile.game_version,
loader: process.instance.loader,
game_version: process.instance.game_version,
source: 'AppBar',
})
} catch (e) {
@@ -395,12 +279,12 @@ const stop = async (process: RunningProcess) => {
await refresh()
}
function goToTerminal(path?: string) {
const selectedPath = path ?? selectedProcess.value?.profile.path
if (!selectedPath) {
function goToTerminal(instanceId?: string) {
const selectedInstanceId = instanceId ?? selectedProcess.value?.instance.id
if (!selectedInstanceId) {
return
}
router.push(`/instance/${encodeURIComponent(selectedPath)}/logs`)
router.push(`/instance/${encodeURIComponent(selectedInstanceId)}/logs`)
}
const currentLoadingBars = ref<LoadingBar[]>([])
@@ -420,8 +304,7 @@ function getLoadingProgress(loadingBar: LoadingBar): number {
}
function getLoadingText(loadingBar: LoadingBar): string {
const percent = Math.floor(getLoadingProgress(loadingBar) * 100)
return loadingBar.message ? `${percent}% ${loadingBar.message}` : `${percent}%`
return loadingBar.message ?? ''
}
function getDisplayIconUrl(icon: string | null | undefined): string | null {
@@ -453,25 +336,33 @@ function removeNotification(): void {
}
function buildDownloadItems(): PopupNotificationProgressItem[] {
return currentLoadingBars.value.map((bar) => ({
id: getLoadingBarKey(bar),
title: bar.title ?? '',
text: getLoadingText(bar),
iconUrl: currentLoadingBarIconUrls.value[getLoadingBarKey(bar)] ?? null,
progress: getLoadingProgress(bar),
waiting: !bar.total || bar.total <= 0,
}))
return [
...installJobNotifications.progressItems.value,
...currentLoadingBars.value.map((bar) => ({
id: getLoadingBarKey(bar),
title: bar.title ?? '',
text: getLoadingText(bar),
iconUrl: currentLoadingBarIconUrls.value[getLoadingBarKey(bar)] ?? null,
progress: getLoadingProgress(bar),
waiting: !bar.total || bar.total <= 0,
progressType: 'percentage',
progressCurrent: bar.current,
progressTotal: bar.total,
})),
]
}
const hasVisibleActiveDownloadToasts = computed(() => !!getNotification())
const hasActiveLoadingBars = computed(() => currentLoadingBars.value.length > 0)
const hasActiveLoadingBars = computed(
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
)
function updateNotification(resummon = false): void {
if (resummon) {
dismissed.value = false
}
if (currentLoadingBars.value.length === 0) {
if (currentLoadingBars.value.length === 0 && !installJobNotifications.active.value) {
removeNotification()
dismissed.value = false
return
@@ -490,17 +381,23 @@ function updateNotification(resummon = false): void {
const progressItems = buildDownloadItems()
if (notif) {
notif.title = formatMessage(messages.downloads)
notif.title = installJobNotifications.active.value
? installJobNotifications.title.value
: formatMessage(messages.downloads)
notif.text = undefined
notif.progressItems = progressItems
notif.buttons = installJobNotifications.buttons.value
notif.progress = undefined
notif.waiting = undefined
} else {
notif = popupNotificationManager.addPopupNotification({
title: formatMessage(messages.downloads),
title: installJobNotifications.active.value
? installJobNotifications.title.value
: formatMessage(messages.downloads),
type: 'download',
autoCloseMs: null,
progressItems,
buttons: installJobNotifications.buttons.value,
})
notificationId.value = notif.id
}
@@ -513,8 +410,8 @@ function formatLoadingBars(loadingBar: LoadingBar): LoadingBar {
version: formatted.bar_type.version,
})
}
if (formatted.bar_type?.profile_path) {
formatted.title = formatted.bar_type.profile_path
if (formatted.bar_type?.instance_id) {
formatted.title = formatted.bar_type.instance_name ?? formatted.bar_type.instance_id
}
if (formatted.bar_type?.pack_name) {
formatted.title = formatted.bar_type.pack_name
@@ -530,31 +427,41 @@ async function refreshLoadingBars() {
currentLoadingBars.value = Object.values(bars)
.map(formatLoadingBars)
.filter((bar) => bar?.bar_type?.type !== 'launcher_update')
.filter(
(bar) =>
bar?.bar_type?.type !== 'launcher_update' &&
![
'java_download',
'pack_file_download',
'pack_download',
'minecraft_download',
'copy_instance',
].includes(bar?.bar_type?.type ?? ''),
)
const profilePaths = Array.from(
const instanceIds = Array.from(
new Set(
currentLoadingBars.value
.map((bar) => bar.bar_type?.profile_path)
.filter((path): path is string => !!path),
.map((bar) => bar.bar_type?.instance_id)
.filter((instanceId): instanceId is string => !!instanceId),
),
)
const profiles = profilePaths.length
? await getInstances(profilePaths).catch((error) => {
const instances = instanceIds.length
? await getInstances(instanceIds).catch((error) => {
handleError(error)
return []
})
: []
const profileIconUrls = new Map(
profiles.map((profile) => [profile.path, getDisplayIconUrl(profile.icon_path)]),
const instanceIconUrls = new Map(
instances.map((instance) => [instance.id, getDisplayIconUrl(instance.icon_path)]),
)
currentLoadingBarIconUrls.value = Object.fromEntries(
currentLoadingBars.value.map((bar) => {
const barIconUrl = getDisplayIconUrl(bar.bar_type?.icon)
const profileIconUrl = bar.bar_type?.profile_path
? profileIconUrls.get(bar.bar_type.profile_path)
const instanceIconUrl = bar.bar_type?.instance_id
? instanceIconUrls.get(bar.bar_type.instance_id)
: null
return [getLoadingBarKey(bar), barIconUrl ?? profileIconUrl ?? null]
return [getLoadingBarKey(bar), barIconUrl ?? instanceIconUrl ?? null]
}),
)
@@ -567,6 +474,12 @@ async function refreshLoadingBars() {
updateNotification()
}
const installJobNotifications = await useInstallJobNotifications({
router,
handleError,
onChange: updateNotification,
})
await refreshLoadingBars()
const unlistenLoading = await loading_listener(async () => {
@@ -588,20 +501,6 @@ onBeforeUnmount(() => {
window.removeEventListener('online', handleOnline)
unlistenProcess()
unlistenLoading()
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
}
installJobNotifications.dispose()
})
</script>
<style scoped>
.update-pill-ready-hidden {
opacity: 0;
transform: scale(0.96);
}
.update-pill-ready-visible {
opacity: 1;
transform: scale(1);
}
</style>
@@ -12,7 +12,7 @@
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
<hr v-if="option.type === 'divider'" class="divider" />
<div
v-else-if="!(isLinkedData(item) && option.name === `add_content`)"
v-else-if="!(isInstanceLink(item) && option.name === `add_content`)"
class="item clickable"
:class="[option.color ?? 'base']"
>
@@ -64,10 +64,10 @@ defineExpose({
},
})
const isLinkedData = (item) => {
if (item.instance != undefined && item.instance.linked_data) {
const isInstanceLink = (item) => {
if (item.instance != undefined && item.instance.link) {
return true
} else if (item != undefined && item.linked_data) {
} else if (item != undefined && item.link) {
return true
}
return false
@@ -16,7 +16,7 @@ import { ChatIcon } from '@/assets/icons'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { install } from '@/helpers/profile.js'
import { install_existing_instance } from '@/helpers/install'
import { cancel_directory_change } from '@/helpers/settings.ts'
import { handleSevereError } from '@/store/error.js'
@@ -72,7 +72,7 @@ defineExpose({
title.value = 'No loader selected'
errorType.value = 'no_loader_version'
supportLink.value = 'https://support.modrinth.com'
metadata.value.profilePath = context.profilePath
metadata.value.instanceId = context.instanceId
} else if (source === 'state_init') {
title.value = 'Error initializing Modrinth App'
errorType.value = 'state_init'
@@ -125,7 +125,7 @@ const loadingRepair = ref(false)
async function repairInstance() {
loadingRepair.value = true
try {
await install(metadata.value.profilePath, false)
await install_existing_instance(metadata.value.instanceId, false)
errorModal.value.hide()
} catch (err) {
handleSevereError(err)
@@ -1,28 +1,32 @@
<script setup>
import { WrenchIcon, XIcon } from '@modrinth/assets'
import { XIcon } from '@modrinth/assets'
import {
Accordion,
ButtonStyled,
Checkbox,
commonMessages,
defineMessages,
FileTreeSelect,
injectNotificationManager,
NewModal,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { save } from '@tauri-apps/plugin-dialog'
import { readDir, stat } from '@tauri-apps/plugin-fs'
import { ref } from 'vue'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
import { PackageIcon } from '@/assets/icons'
import {
export_instance_mrpack,
get_full_path,
get_pack_export_candidates,
} from '@/helpers/instance'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const messages = defineMessages({
header: { id: 'app.export-modal.header', defaultMessage: 'Export modpack' },
modpackNameLabel: { id: 'app.export-modal.modpack-name-label', defaultMessage: 'Modpack Name' },
modpackNameLabel: { id: 'app.export-modal.modpack-name-label', defaultMessage: 'Modpack name' },
modpackNamePlaceholder: {
id: 'app.export-modal.modpack-name-placeholder',
defaultMessage: 'Modpack name',
@@ -39,15 +43,7 @@ const messages = defineMessages({
id: 'app.export-modal.description-placeholder',
defaultMessage: 'Enter modpack description...',
},
selectFilesLabel: {
id: 'app.export-modal.select-files-label',
defaultMessage: 'Configure which files are included in this export',
},
exportButton: { id: 'app.export-modal.export-button', defaultMessage: 'Export' },
includeFile: {
id: 'app.export-modal.include-file-accessibility-label',
defaultMessage: 'Include "{file}"?',
},
})
const props = defineProps({
@@ -59,8 +55,9 @@ const props = defineProps({
defineExpose({
show: () => {
resetExportState()
exportModal.value.show()
initFiles()
void initFiles().catch(handleError)
},
})
@@ -69,67 +66,33 @@ const nameInput = ref(props.instance.name)
const exportDescription = ref('')
const versionInput = ref('1.0.0')
const files = ref([])
const folders = ref([])
const selectedFilePaths = ref([])
const fileTreeKey = ref(0)
const filesLoadId = ref(0)
const instanceRoot = ref('')
const loadedDirectories = ref(new Set())
const initFiles = async () => {
const newFolders = new Map()
const sep = '/'
files.value = []
await get_pack_export_candidates(props.instance.path).then((filePaths) =>
filePaths
.map((folder) => ({
path: folder,
name: folder.split(sep).pop(),
selected:
folder.startsWith('mods') ||
folder.startsWith('datapacks') ||
folder.startsWith('resourcepacks') ||
folder.startsWith('shaderpacks') ||
folder.startsWith('config'),
disabled:
folder === 'profile.json' ||
folder.startsWith('modrinth_logs') ||
folder.startsWith('.fabric'),
}))
.filter(
(pathData) =>
!pathData.path.includes('.DS_Store') &&
pathData.path !== 'mods/.connector' &&
!pathData.path.startsWith('mods/.connector/'),
)
.forEach((pathData) => {
const parent = pathData.path.split(sep).slice(0, -1).join(sep)
if (parent !== '') {
if (newFolders.has(parent)) {
newFolders.get(parent).push(pathData)
} else {
newFolders.set(parent, [pathData])
}
} else {
files.value.push(pathData)
}
}),
)
folders.value = [...newFolders.entries()].map(([name, value]) => [
{
name,
showingMore: false,
},
value,
async function initFiles() {
const loadId = ++filesLoadId.value
const [filePaths, root] = await Promise.all([
get_pack_export_candidates(props.instance.id),
get_full_path(props.instance.id),
])
if (loadId !== filesLoadId.value) return
instanceRoot.value = root
const exportCandidates = await Promise.all(
filePaths.map((path) => buildExportCandidateItem(root, path)),
)
if (loadId !== filesLoadId.value) return
files.value = exportCandidates
selectedFilePaths.value = files.value
.filter((file) => !file.disabled && isDefaultSelectedExportCandidate(file.path))
.map((file) => file.path)
}
await initFiles()
const exportPack = async () => {
const filesToExport = files.value.filter((file) => file.selected).map((file) => file.path)
folders.value.forEach((args) => {
args[1].forEach((child) => {
if (child.selected) {
filesToExport.push(child.path)
}
})
})
const outputPath = await save({
defaultPath: `${nameInput.value} ${versionInput.value}.mrpack`,
filters: [
@@ -141,10 +104,10 @@ const exportPack = async () => {
})
if (outputPath) {
export_profile_mrpack(
props.instance.path,
export_instance_mrpack(
props.instance.id,
outputPath,
filesToExport,
selectedFilePaths.value,
versionInput.value,
exportDescription.value,
nameInput.value,
@@ -152,94 +115,176 @@ const exportPack = async () => {
exportModal.value.hide()
}
}
function resetExportState() {
nameInput.value = props.instance.name
exportDescription.value = ''
versionInput.value = '1.0.0'
files.value = []
selectedFilePaths.value = []
fileTreeKey.value += 1
instanceRoot.value = ''
loadedDirectories.value = new Set()
}
async function loadExportDirectory(path) {
if (!path || !instanceRoot.value || loadedDirectories.value.has(path)) return
const loadId = filesLoadId.value
loadedDirectories.value.add(path)
try {
const entries = await readDir(`${instanceRoot.value}/${path}`)
const childItems = await Promise.all(
entries.map((entry) => buildExportDirectoryChildItem(instanceRoot.value, path, entry)),
)
if (loadId !== filesLoadId.value) return
appendExportItems(childItems)
} catch {
loadedDirectories.value.delete(path)
}
}
async function buildExportCandidateItem(instanceRoot, path) {
try {
const entries = await readDir(`${instanceRoot}/${path}`)
const metadata = await getExportCandidateMetadata(instanceRoot, path)
return {
path,
type: 'directory',
disabled: isExportCandidateDisabled(path),
modified: metadata.modified,
count: entries.length,
}
} catch {
return buildExportFileItem(instanceRoot, path)
}
}
async function buildExportDirectoryChildItem(instanceRoot, parentPath, entry) {
const path = `${parentPath}/${entry.name}`
if (entry.isDirectory) {
const metadata = await getExportCandidateMetadata(instanceRoot, path)
return {
path,
type: 'directory',
disabled: isExportCandidateDisabled(path),
modified: metadata.modified,
}
}
return buildExportFileItem(instanceRoot, path)
}
async function buildExportFileItem(instanceRoot, path) {
const metadata = await getExportCandidateMetadata(instanceRoot, path)
return {
path,
type: 'file',
disabled: isExportCandidateDisabled(path),
size: metadata.size,
modified: metadata.modified,
}
}
function appendExportItems(items) {
const nextFiles = new Map(files.value.map((file) => [normalizeExportPath(file.path), file]))
for (const item of items) {
nextFiles.set(normalizeExportPath(item.path), item)
}
files.value = [...nextFiles.values()]
}
async function getExportCandidateMetadata(instanceRoot, path) {
try {
const metadata = await stat(`${instanceRoot}/${path}`)
return {
size: metadata.size,
modified: metadata.mtime ? Math.floor(metadata.mtime.getTime() / 1000) : undefined,
}
} catch {
return {}
}
}
function normalizeExportPath(path) {
return path.replaceAll('\\', '/').split('/').filter(Boolean).join('/')
}
function isDefaultSelectedExportCandidate(path) {
return (
path.startsWith('mods') ||
path.startsWith('datapacks') ||
path.startsWith('resourcepacks') ||
path.startsWith('shaderpacks') ||
path.startsWith('config')
)
}
function isExportCandidateDisabled(path) {
return (
path === 'profile.json' ||
path.startsWith('modrinth_logs') ||
path.startsWith('.fabric') ||
path.startsWith('__MACOSX')
)
}
</script>
<template>
<ModalWrapper ref="exportModal" :header="formatMessage(messages.header)">
<div class="flex flex-col gap-4 w-[40rem]">
<NewModal
ref="exportModal"
:header="formatMessage(messages.header)"
scrollable
width="46rem"
max-width="calc(100vw - 2rem)"
>
<div class="flex flex-col gap-4">
<div class="grid grid-cols-2 gap-4">
<div class="labeled_input">
<p>{{ formatMessage(messages.modpackNameLabel) }}</p>
<div class="labeled_input w-full">
<p class="text-contrast font-semibold">{{ formatMessage(messages.modpackNameLabel) }}</p>
<StyledInput
v-model="nameInput"
:icon="PackageIcon"
type="text"
:placeholder="formatMessage(messages.modpackNamePlaceholder)"
clearable
wrapper-class="w-full"
/>
</div>
<div class="labeled_input">
<p>{{ formatMessage(messages.versionNumberLabel) }}</p>
<div class="labeled_input w-full">
<p class="text-contrast font-semibold">
{{ formatMessage(messages.versionNumberLabel) }}
</p>
<StyledInput
v-model="versionInput"
:icon="VersionIcon"
type="text"
:placeholder="formatMessage(messages.versionNumberPlaceholder)"
clearable
wrapper-class="w-full"
/>
</div>
</div>
<div class="flex flex-col gap-2">
<p class="m-0">{{ formatMessage(commonMessages.descriptionLabel) }}</p>
<div class="flex flex-col gap-2 min-w-0">
<p class="m-0 text-contrast font-semibold">
{{ formatMessage(commonMessages.descriptionLabel) }}
</p>
<StyledInput
v-model="exportDescription"
multiline
:placeholder="formatMessage(messages.descriptionPlaceholder)"
wrapper-class="w-full"
/>
</div>
<Accordion
class="w-full bg-surface-4 border border-solid border-surface-5 rounded-2xl overflow-clip"
button-class="p-4 w-full border-b border-solid border-b-surface-5 bg-surface-2 -mb-px hover:brightness-[--hover-brightness] group"
>
<template #title>
<span class="flex items-center gap-3 text-contrast group-active:scale-[0.98]">
<WrenchIcon aria-hidden="true" class="size-5 text-secondary" />
Configure which files are included in this export
</span>
</template>
<div class="flex flex-col [&>*:nth-child(even)]:bg-surface-3">
<div v-for="[path, children] in folders" :key="path.name" class="flex flex-col">
<Accordion
class="flex flex-col"
button-class="flex gap-3 pr-4 hover:bg-surface-5 group"
>
<template #title>
<Checkbox
:model-value="children.every((child) => child.selected)"
:indeterminate="
!children.every((child) => child.selected) &&
children.some((child) => child.selected)
"
:description="formatMessage(messages.includeFile, { file: path.name })"
class="pl-4 py-2"
:disabled="children.every((x) => x.disabled)"
@update:model-value="
(newValue) => children.forEach((child) => (child.selected = newValue))
"
@click.stop
/>
<span class="ml-2 group-active:scale-95">{{ path.name }}/</span>
</template>
<div v-for="child in children" :key="child.path">
<Checkbox
v-model="child.selected"
:label="child.name"
class="w-full px-8 py-2 hover:bg-surface-4 text-primary"
:disabled="child.disabled"
/>
</div>
</Accordion>
</div>
<Checkbox
v-for="file in files"
:key="file.path"
v-model="file.selected"
:label="file.name"
:disabled="file.disabled"
class="w-full px-4 py-2 hover:bg-surface-4 text-primary"
/>
</div>
</Accordion>
<FileTreeSelect
:key="fileTreeKey"
v-model="selectedFilePaths"
class="min-w-0"
:items="files"
@navigate="loadExportDirectory"
/>
</div>
<template #actions>
<div class="flex items-center justify-end gap-2">
<ButtonStyled type="outlined">
<button @click="exportModal.hide">
@@ -254,6 +299,6 @@ const exportPack = async () => {
</button>
</ButtonStyled>
</div>
</div>
</ModalWrapper>
</template>
</NewModal>
</template>
@@ -15,9 +15,10 @@ import { useRouter } from 'vue-router'
import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events'
import { get_by_profile_path } from '@/helpers/process'
import { finish_install, kill, run } from '@/helpers/profile'
import { showProfileInFolder } from '@/helpers/utils.js'
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
import { kill, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import { showInstanceInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js'
const { handleError } = injectNotificationManager()
@@ -54,11 +55,11 @@ const installed = computed(() => props.instance.install_stage === 'installed')
const router = useRouter()
const seeInstance = async () => {
await router.push(`/instance/${encodeURIComponent(props.instance.path)}`)
await router.push(`/instance/${encodeURIComponent(props.instance.id)}`)
}
const checkProcess = async () => {
const runningProcesses = await get_by_profile_path(props.instance.path).catch(handleError)
const runningProcesses = await get_by_instance_id(props.instance.id).catch(handleError)
playing.value = runningProcesses.length > 0
}
@@ -66,8 +67,8 @@ const checkProcess = async () => {
const play = async (e, context) => {
e?.stopPropagation()
loading.value = true
await run(props.instance.path)
.catch((err) => handleSevereError(err, { profilePath: props.instance.path }))
await run(props.instance.id)
.catch((err) => handleSevereError(err, { instanceId: props.instance.id }))
.finally(() => {
trackEvent('InstanceStart', {
loader: props.instance.loader,
@@ -82,7 +83,7 @@ const stop = async (e, context) => {
e?.stopPropagation()
playing.value = false
await kill(props.instance.path).catch(handleError)
await kill(props.instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: props.instance.loader,
@@ -94,17 +95,30 @@ const stop = async (e, context) => {
const repair = async (e) => {
e?.stopPropagation()
await finish_install(props.instance).catch(handleError)
if (
props.instance.install_stage !== 'pack_installed' &&
(props.instance.link?.type === 'modrinth_modpack' ||
props.instance.link?.type === 'server_project_modpack')
) {
await install_pack_to_existing_instance(props.instance.id, {
type: 'fromVersionId',
project_id: props.instance.link.project_id ?? props.instance.link.server_project_id ?? '',
version_id: props.instance.link.version_id ?? props.instance.link.content_version_id ?? '',
title: props.instance.name,
}).catch(handleError)
} else {
await install_existing_instance(props.instance.id, false).catch(handleError)
}
}
const openFolder = async () => {
await showProfileInFolder(props.instance.path)
await showInstanceInFolder(props.instance.id)
}
const addContent = async () => {
await router.push({
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
query: { i: props.instance.path },
query: { i: props.instance.id },
})
}
@@ -120,7 +134,7 @@ defineExpose({
const currentEvent = ref(null)
const unlisten = await process_listener((e) => {
if (e.profile_path_id === props.instance.path) {
if (e.instance_id === props.instance.id) {
currentEvent.value = e.event
if (e.event === 'finished') {
playing.value = false
@@ -142,7 +156,7 @@ onUnmounted(() => unlisten())
<Avatar
size="48px"
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
:tint-by="instance.path"
:tint-by="instance.id"
alt="Mod card"
/>
<div class="h-full flex items-center font-bold text-contrast leading-normal">
@@ -191,7 +205,7 @@ onUnmounted(() => unlisten())
<Avatar
size="48px"
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
:tint-by="instance.path"
:tint-by="instance.id"
alt="Mod card"
:class="`transition-all ${modLoading || installing ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
/>
@@ -22,7 +22,7 @@ const props = withDefaults(
)
const instanceLink = computed(() => {
const base = `/instance/${encodeURIComponent(props.instance.path)}`
const base = `/instance/${encodeURIComponent(props.instance.id)}`
return props.backTab ? `${base}/${props.backTab}` : base
})
</script>
@@ -5,7 +5,7 @@ import { computed, ref } from 'vue'
import { SwapIcon } from '@/assets/icons/index.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { update_managed_modrinth_version } from '@/helpers/profile'
import { update_managed_modrinth_version } from '@/helpers/instance'
import { releaseColor } from '@/helpers/utils'
const props = defineProps({
@@ -32,14 +32,14 @@ const filteredVersions = computed(() => {
})
const modpackVersionModal = ref(null)
const installedVersion = computed(() => props.instance?.linked_data?.version_id)
const installedVersion = computed(() => props.instance?.link?.version_id)
const installing = computed(() => props.instance.install_stage !== 'installed')
const inProgress = ref(false)
const switchVersion = async (versionId) => {
modpackVersionModal.value.hide()
inProgress.value = true
await update_managed_modrinth_version(props.instance.path, versionId)
await update_managed_modrinth_version(props.instance.id, versionId)
inProgress.value = false
emit('finish-install')
}
@@ -59,7 +59,7 @@ const onHide = () => {
:on-hide="onHide"
>
<div class="modal-body">
<div v-if="instance.linked_data" class="mod-card">
<div v-if="instance.link" class="mod-card">
<div class="table">
<div class="table-row with-columns table-head">
<div class="table-cell table-text download-cell" />
@@ -6,16 +6,16 @@ import dayjs from 'dayjs'
import { onUnmounted, ref } from 'vue'
import NavButton from '@/components/ui/NavButton.vue'
import { profile_listener } from '@/helpers/events.js'
import { list } from '@/helpers/profile'
import { instance_listener } from '@/helpers/events.js'
import { list } from '@/helpers/instance'
const { handleError } = injectNotificationManager()
const recentInstances = ref([])
const getInstances = async () => {
const profiles = await list().catch(handleError)
const instances = await list().catch(handleError)
recentInstances.value = profiles
recentInstances.value = instances
.sort((a, b) => {
const dateACreated = dayjs(a.created)
const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0)
@@ -37,24 +37,24 @@ const getInstances = async () => {
await getInstances()
const unlistenProfile = await profile_listener(async (event) => {
const unlistenInstance = await instance_listener(async (event) => {
if (event.event !== 'synced') {
await getInstances()
}
})
onUnmounted(() => {
unlistenProfile()
unlistenInstance()
})
</script>
<template>
<div v-for="instance in recentInstances" :key="instance.id" v-tooltip.right="instance.name">
<NavButton :to="`/instance/${encodeURIComponent(instance.path)}`" class="relative">
<NavButton :to="`/instance/${encodeURIComponent(instance.id)}`" class="relative">
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
size="28px"
:tint-by="instance.path"
:tint-by="instance.id"
:class="`transition-all ${instance.install_stage !== 'installed' ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
/>
<div
@@ -65,10 +65,7 @@ onUnmounted(() => {
</div>
</NavButton>
</div>
<div
v-if="instances && recentInstances.length > 0"
class="h-px w-6 mx-auto my-2 bg-divider"
></div>
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-divider"></div>
</template>
<style scoped lang="scss"></style>
@@ -0,0 +1,124 @@
<template>
<ButtonStyled color="brand" type="outlined" hover-color-fill="background">
<button
v-if="showUpdatePill"
type="button"
class="!h-[34px] text-sm !transition-[opacity,transform,background-color,color,filter] !duration-200 ease-out"
:class="{
'opacity-0 scale-[0.96]': finishedDownloading && !animateReadyPill,
'opacity-100 scale-100': finishedDownloading && animateReadyPill,
}"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" :class="{ 'animate-spin': restarting }" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">{{ downloadPercent }}%</span>
</span>
<span v-else>{{ updateLabel }}</span>
</button>
</ButtonStyled>
</template>
<script setup lang="ts">
import { DownloadIcon, RefreshCwIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import {
appUpdateState,
downloadAvailableAppUpdate,
installAvailableAppUpdate,
} from '@/providers/app-update'
const { formatMessage } = useVIntl()
const messages = defineMessages({
update: {
id: 'app.action-bar.update',
defaultMessage: 'Update',
},
downloadingUpdate: {
id: 'app.action-bar.downloading-update',
defaultMessage: 'Downloading update',
},
reloadToUpdate: {
id: 'app.action-bar.reload-to-update',
defaultMessage: 'Reload to update',
},
})
const {
downloading,
downloadPercent,
downloadProgress,
finishedDownloading,
isVisible: isUpdateVisible,
metered,
restarting,
} = appUpdateState
const isUpdateDownloading = computed(
() =>
downloading.value ||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
)
const showUpdatePill = computed(
() => isUpdateVisible.value && (finishedDownloading.value || metered.value),
)
const animateReadyPill = ref(false)
const updateLabel = computed(() => {
if (isUpdateDownloading.value) {
return formatMessage(messages.downloadingUpdate)
}
if (finishedDownloading.value) {
return formatMessage(messages.reloadToUpdate)
}
return formatMessage(messages.update)
})
let readyPillAnimationFrame: number | null = null
watch([showUpdatePill, finishedDownloading], async ([show, ready], [wasShown, wasReady]) => {
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
readyPillAnimationFrame = null
}
if (!show || !ready) {
animateReadyPill.value = false
return
}
if (wasShown && wasReady) {
return
}
animateReadyPill.value = false
await nextTick()
readyPillAnimationFrame = requestAnimationFrame(() => {
animateReadyPill.value = true
readyPillAnimationFrame = null
})
})
async function handleUpdateClick() {
if (isUpdateDownloading.value) {
return
}
if (finishedDownloading.value) {
await installAvailableAppUpdate()
} else {
await downloadAvailableAppUpdate()
}
}
onBeforeUnmount(() => {
if (readyPillAnimationFrame !== null) {
cancelAnimationFrame(readyPillAnimationFrame)
}
})
</script>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { MailIcon, SendIcon, UserIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
import { MailIcon, SearchIcon, SendIcon, UserIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
@@ -301,11 +301,12 @@ const messages = defineMessages({
</ButtonStyled>
<StyledInput
v-model="search"
:icon="SearchIcon"
type="text"
:placeholder="formatMessage(messages.searchFriends)"
clearable
variant="outlined"
wrapper-class="flex-1"
input-class="!bg-transparent !border !border-solid !border-button-bg !text-primary !placeholder:text-primary"
wrapper-class="flex-1 [&>svg]:!text-primary [&>svg]:!opacity-100"
@keyup.esc="search = ''"
/>
</template>
@@ -7,26 +7,28 @@ import {
injectNotificationManager,
StyledInput,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { convertFileSrc } from '@tauri-apps/api/core'
import { computed, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { list } from '@/helpers/profile'
import { add_server_to_profile, get_profile_worlds } from '@/helpers/worlds.ts'
import { list } from '@/helpers/instance'
import { add_server_to_instance, get_instance_worlds } from '@/helpers/worlds.ts'
const { handleError } = injectNotificationManager()
const queryClient = useQueryClient()
const modal = ref()
const searchFilter = ref('')
const profiles = ref([])
const instances = ref([])
const serverName = ref('')
const serverAddress = ref('')
const shownProfiles = computed(() =>
profiles.value.filter((profile) => {
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
const shownInstances = computed(() =>
instances.value.filter((instance) => {
return instance.name.toLowerCase().includes(searchFilter.value.toLowerCase())
}),
)
@@ -36,15 +38,15 @@ defineExpose({
serverAddress.value = address
searchFilter.value = ''
const profilesVal = await list().catch(handleError)
const instanceValues = await list().catch(handleError)
await Promise.allSettled(
profilesVal.map(async (profile) => {
profile.adding = false
profile.added = false
instanceValues.map(async (instance) => {
instance.adding = false
instance.added = false
try {
const worlds = await get_profile_worlds(profile.path)
profile.added = worlds.some(
const worlds = await get_instance_worlds(instance.id)
instance.added = worlds.some(
(w) => w.type === 'server' && w.address === serverAddress.value,
)
} catch {
@@ -53,28 +55,29 @@ defineExpose({
}),
)
profiles.value = profilesVal
instances.value = instanceValues
modal.value.show()
trackEvent('AddServerToInstanceStart', { source: 'AddServerToInstanceModal' })
},
})
async function addServer(profile) {
profile.adding = true
async function addServer(instance) {
instance.adding = true
try {
await add_server_to_profile(profile.path, serverName.value, serverAddress.value, 'prompt')
profile.added = true
await add_server_to_instance(instance.id, serverName.value, serverAddress.value, 'prompt')
instance.added = true
await queryClient.invalidateQueries({ queryKey: ['worlds', instance.id] })
trackEvent('AddServerToInstance', {
server_name: serverName.value,
instance_name: profile.name,
instance_name: instance.name,
source: 'AddServerToInstanceModal',
})
} catch (err) {
handleError(err)
}
profile.adding = false
instance.adding = false
}
</script>
@@ -91,26 +94,26 @@ async function addServer(profile) {
/>
<div class="max-h-[21rem] overflow-y-auto">
<div
v-for="profile in shownProfiles"
:key="profile.path"
v-for="instance in shownInstances"
:key="instance.id"
class="flex w-full items-center justify-between gap-2 bg-bg-raised text-icon shadow-none"
>
<router-link
class="btn btn-transparent p-2 text-left"
:to="`/instance/${encodeURIComponent(profile.path)}`"
:to="`/instance/${encodeURIComponent(instance.id)}`"
@click="modal.hide()"
>
<Avatar
:src="profile.icon_path ? convertFileSrc(profile.icon_path) : null"
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
class="mr-2 [--size:2rem]"
/>
{{ profile.name }}
{{ instance.name }}
</router-link>
<ButtonStyled>
<button :disabled="profile.added || profile.adding" @click="addServer(profile)">
<PlusIcon v-if="!profile.added && !profile.adding" />
<CheckIcon v-else-if="profile.added" />
{{ profile.adding ? 'Adding...' : profile.added ? 'Added' : 'Add' }}
<button :disabled="instance.added || instance.adding" @click="addServer(instance)">
<PlusIcon v-if="!instance.added && !instance.adding" />
<CheckIcon v-else-if="instance.added" />
{{ instance.adding ? 'Adding...' : instance.added ? 'Added' : 'Add' }}
</button>
</ButtonStyled>
</div>
@@ -19,7 +19,8 @@ import { useRouter } from 'vue-router'
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
import { install_duplicate_instance } from '@/helpers/install'
import { edit, edit_icon, list, remove } from '@/helpers/instance'
import { injectInstanceSettings } from '@/providers/instance-settings'
import type { GameInstance } from '../../../helpers/types'
@@ -32,14 +33,14 @@ const queryClient = useQueryClient()
const deleteConfirmModal = ref()
const { instance } = injectInstanceSettings()
type ReleaseChannel = GameInstance['preferred_update_channel']
type ReleaseChannel = GameInstance['update_channel']
const releaseChannelOptions: ReleaseChannel[] = ['release', 'beta', 'alpha']
const title = ref(instance.value.name)
const icon: Ref<string | undefined> = ref(instance.value.icon_path)
const groups = ref([...instance.value.groups])
const savingReleaseChannel = ref(false)
const selectedReleaseChannel = ref<ReleaseChannel>(instance.value.preferred_update_channel)
const selectedReleaseChannel = ref<ReleaseChannel>(instance.value.update_channel)
const releaseChannelDisabledItems = computed<ReleaseChannel[]>(() =>
savingReleaseChannel.value ? [...releaseChannelOptions] : [],
)
@@ -48,8 +49,8 @@ const newCategoryInput = ref('')
const installing = computed(() => instance.value.install_stage !== 'installed')
async function duplicateProfile() {
await duplicate(instance.value.path).catch(handleError)
async function duplicateInstance() {
await install_duplicate_instance(instance.value.id).catch(handleError)
trackEvent('InstanceDuplicate', {
loader: instance.value.loader,
game_version: instance.value.game_version,
@@ -84,22 +85,22 @@ function formatReleaseChannelDescription(channel: ReleaseChannel) {
}
watch(
() => [instance.value.path, instance.value.preferred_update_channel] as const,
() => [instance.value.id, instance.value.update_channel] as const,
() => {
if (!savingReleaseChannel.value) {
selectedReleaseChannel.value = instance.value.preferred_update_channel
selectedReleaseChannel.value = instance.value.update_channel
}
},
)
watch(selectedReleaseChannel, async (channel, previousChannel) => {
const previousReleaseChannel = previousChannel ?? instance.value.preferred_update_channel
if (channel === instance.value.preferred_update_channel) return
const previousReleaseChannel = previousChannel ?? instance.value.update_channel
if (channel === instance.value.update_channel) return
savingReleaseChannel.value = true
const profilePath = instance.value.path
await edit(profilePath, { preferred_update_channel: channel })
.then(() => queryClient.invalidateQueries({ queryKey: ['linkedModpackInfo', profilePath] }))
const instanceId = instance.value.id
await edit(instanceId, { update_channel: channel })
.then(() => queryClient.invalidateQueries({ queryKey: ['linkedModpackInfo', instanceId] }))
.catch((error) => {
selectedReleaseChannel.value = previousReleaseChannel
handleError(error)
@@ -109,7 +110,7 @@ watch(selectedReleaseChannel, async (channel, previousChannel) => {
async function resetIcon() {
icon.value = undefined
await edit_icon(instance.value.path, null).catch(handleError)
await edit_icon(instance.value.id, null).catch(handleError)
trackEvent('InstanceRemoveIcon')
}
@@ -127,12 +128,12 @@ async function setIcon() {
if (!value) return
icon.value = value
await edit_icon(instance.value.path, icon.value).catch(handleError)
await edit_icon(instance.value.id, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')
}
const editProfileObject = computed(() => ({
const editInstanceObject = computed(() => ({
name: title.value.trim().substring(0, 32) ?? 'Instance',
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
}))
@@ -158,15 +159,15 @@ watch(
[title, groups, groups],
async () => {
if (removing.value) return
await edit(instance.value.path, editProfileObject.value).catch(handleError)
await edit(instance.value.id, editInstanceObject.value).catch(handleError)
},
{ deep: true },
)
const removing = ref(false)
async function removeProfile() {
async function removeInstance() {
removing.value = true
const path = instance.value.path
const path = instance.value.id
trackEvent('InstanceRemove', {
loader: instance.value.loader,
@@ -284,7 +285,7 @@ const messages = defineMessages({
</script>
<template>
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="removeProfile" />
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="removeInstance" />
<div class="block">
<div class="float-end ml-10 relative group w-fit">
<div class="flex flex-col gap-1">
@@ -310,7 +311,7 @@ const messages = defineMessages({
:src="icon ? convertFileSrc(icon) : icon"
size="108px"
class="transition-[filter] group-hover:brightness-75"
:tint-by="instance.path"
:tint-by="instance.id"
no-shadow
/>
<div
@@ -350,7 +351,7 @@ const messages = defineMessages({
aria-labelledby="duplicate-instance-label"
:disabled="installing"
class="w-max !shadow-none"
@click="duplicateProfile"
@click="duplicateInstance"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</button>
@@ -8,7 +8,7 @@ import {
} from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
import { edit } from '@/helpers/profile'
import { edit } from '@/helpers/instance'
import { get } from '@/helpers/settings.ts'
import { injectInstanceSettings } from '@/providers/instance-settings'
@@ -28,21 +28,21 @@ const overrideHooks = ref(
)
const hooks = ref(instance.value.hooks ?? globalSettings.hooks)
const editProfileObject = computed(() => {
const editProfile: {
const editInstanceObject = computed(() => {
const editInstancePatch: {
hooks?: Hooks
} = {}
// When hooks are not overridden per-instance, we want to clear them
editProfile.hooks = overrideHooks.value ? hooks.value : {}
editInstancePatch.hooks = overrideHooks.value ? hooks.value : {}
return editProfile
return editInstancePatch
})
watch(
[overrideHooks, hooks],
async () => {
await edit(instance.value.path, editProfileObject.value)
await edit(instance.value.id, editInstanceObject.value)
},
{ deep: true },
)
@@ -4,6 +4,7 @@ import {
commonMessages,
defineMessages,
formatLoaderLabel,
injectFilePicker,
injectNotificationManager,
InstallationSettingsLayout,
provideAppBackup,
@@ -13,89 +14,114 @@ import {
} from '@modrinth/ui'
import type { GameVersionTag, PlatformTag } from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref, shallowRef } from 'vue'
import { computed, ref } from 'vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_versions, get_version } from '@/helpers/cache'
import { get_loader_versions } from '@/helpers/metadata'
import {
duplicate,
install_duplicate_instance,
install_existing_instance,
install_pack_to_existing_instance,
installJobInstanceId,
wait_for_install_job,
} from '@/helpers/install'
import {
edit,
get_linked_modpack_info,
install,
list,
update_managed_modrinth_version,
update_repair_modrinth,
} from '@/helpers/profile'
} from '@/helpers/instance'
import { get_loader_versions } from '@/helpers/metadata'
import { get_game_versions, get_loaders } from '@/helpers/tags'
import { injectInstanceSettings } from '@/providers/instance-settings'
import { useTheming } from '@/store/state'
import type { Manifest } from '../../../helpers/types'
const { handleError } = injectNotificationManager()
const filePicker = injectFilePicker()
const { formatMessage } = useVIntl()
const queryClient = useQueryClient()
const debug = useDebugLogger('AppInstallationSettings')
const themeStore = useTheming()
const { instance, offline, isMinecraftServer, onUnlinked, closeModal } = injectInstanceSettings()
const skipNonEssentialWarnings = computed(() =>
themeStore.getFeatureFlag('skip_non_essential_warnings'),
)
debug('metadata load: start', {
instancePath: instance.value.path,
instanceId: instance.value.id,
loader: instance.value.loader,
gameVersion: instance.value.game_version,
installStage: instance.value.install_stage,
})
const [
fabric_versions,
forge_versions,
quilt_versions,
neoforge_versions,
all_game_versions,
loaders,
] = await Promise.all([
get_loader_versions('fabric')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_loader_versions('forge')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_loader_versions('quilt')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_loader_versions('neo')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_game_versions()
.then((gameVersions: GameVersionTag[]) => shallowRef(gameVersions))
.catch(handleError),
get_loaders()
.then((value: PlatformTag[]) =>
value
.filter(
(item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla',
)
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
)
.then((loader: PlatformTag[]) => ref(loader))
.catch(handleError),
])
function getSupportedModpackLoaders() {
return get_loaders().then((value: PlatformTag[]) =>
value
.filter((item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla')
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
)
}
debug('metadata load: done', {
hasFabricManifest: !!fabric_versions?.value,
hasForgeManifest: !!forge_versions?.value,
hasQuiltManifest: !!quilt_versions?.value,
hasNeoforgeManifest: !!neoforge_versions?.value,
gameVersions: all_game_versions?.value?.length ?? 0,
availablePlatforms: loaders?.value?.map((loader) => loader.name) ?? [],
const fabricVersionsQuery = useQuery({
queryKey: ['instance-settings', 'loader-versions', 'fabric'],
queryFn: () => get_loader_versions('fabric') as Promise<Manifest>,
})
const forgeVersionsQuery = useQuery({
queryKey: ['instance-settings', 'loader-versions', 'forge'],
queryFn: () => get_loader_versions('forge') as Promise<Manifest>,
})
const quiltVersionsQuery = useQuery({
queryKey: ['instance-settings', 'loader-versions', 'quilt'],
queryFn: () => get_loader_versions('quilt') as Promise<Manifest>,
})
const neoforgeVersionsQuery = useQuery({
queryKey: ['instance-settings', 'loader-versions', 'neo'],
queryFn: () => get_loader_versions('neo') as Promise<Manifest>,
})
const gameVersionsQuery = useQuery({
queryKey: ['instance-settings', 'game-versions'],
queryFn: () => get_game_versions() as Promise<GameVersionTag[]>,
})
const loadersQuery = useQuery({
queryKey: ['instance-settings', 'loaders', 'modpack'],
queryFn: getSupportedModpackLoaders,
})
const { data: modpackInfo } = useQuery({
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 metadataLoading = computed(() =>
[
fabricVersionsQuery,
forgeVersionsQuery,
quiltVersionsQuery,
neoforgeVersionsQuery,
gameVersionsQuery,
loadersQuery,
].some((query) => query.isLoading.value),
)
debug('metadata queries configured', {
instanceId: instance.value.id,
loader: instance.value.loader,
gameVersion: instance.value.game_version,
})
const isModrinthLinkedModpack = computed(
() =>
instance.value.link?.type === 'modrinth_modpack' ||
instance.value.link?.type === 'server_project_modpack',
)
const isImportedModpack = computed(() => instance.value.link?.type === 'imported_modpack')
const modpackInfoQuery = useQuery({
queryKey: computed(() => ['linkedModpackInfo', instance.value.id]),
queryFn: () => get_linked_modpack_info(instance.value.id, 'must_revalidate'),
enabled: computed(() => isModrinthLinkedModpack.value && !offline),
})
const modpackInfo = modpackInfoQuery.data
const repairing = ref(false)
const reinstalling = ref(false)
@@ -107,43 +133,60 @@ const messages = defineMessages({
})
function getManifest(loader: string) {
const map: Record<string, typeof fabric_versions> = {
fabric: fabric_versions,
forge: forge_versions,
quilt: quilt_versions,
neoforge: neoforge_versions,
const map: Record<string, Manifest | undefined> = {
fabric: fabricVersionsQuery.data.value,
forge: forgeVersionsQuery.data.value,
quilt: quiltVersionsQuery.data.value,
neoforge: neoforgeVersionsQuery.data.value,
}
const manifest = map[loader]
debug('getManifest:', {
loader,
hasManifest: !!manifest?.value,
gameVersions: manifest?.value?.gameVersions?.length ?? 0,
hasManifest: !!manifest,
gameVersions: manifest?.gameVersions?.length ?? 0,
})
return manifest
}
async function installLocalModpackFromPicker() {
const picked = await filePicker.pickModpackFile({ readFile: false })
if (!picked?.path) return false
const job = await install_pack_to_existing_instance(instance.value.id, {
type: 'fromFile',
path: picked.path,
}).catch(handleError)
if (!job) return false
const completed = await wait_for_install_job(job.job_id).catch(handleError)
return !!completed
}
provideAppBackup({
async createBackup() {
debug('createBackup: start', {
instancePath: instance.value.path,
instanceId: instance.value.id,
instanceName: instance.value.name,
})
const allProfiles = await list()
const allInstances = await list()
const prefix = `${instance.value.name} - Backup #`
const existingNums = allProfiles
const existingNums = allInstances
.filter((p) => p.name.startsWith(prefix))
.map((p) => parseInt(p.name.slice(prefix.length), 10))
.filter((n) => !isNaN(n))
const nextNum = existingNums.length > 0 ? Math.max(...existingNums) + 1 : 1
const newPath = await duplicate(instance.value.path)
await edit(newPath, { name: `${prefix}${nextNum}` })
debug('createBackup: done', { newPath, backupName: `${prefix}${nextNum}` })
const job = await install_duplicate_instance(instance.value.id)
const newInstanceId = installJobInstanceId(job)
if (newInstanceId) {
await edit(newInstanceId, { name: `${prefix}${nextNum}` })
}
debug('createBackup: done', { newInstanceId, backupName: `${prefix}${nextNum}` })
},
})
provideInstallationSettings({
closeSettings: closeModal,
loading: ref(false),
loading: computed(() => metadataLoading.value || modpackInfoQuery.isLoading.value),
installationInfo: computed(() => {
const rows = [
{
@@ -165,7 +208,7 @@ provideInstallationSettings({
}
return rows
}),
isLinked: computed(() => !!instance.value.linked_data?.locked),
isLinked: computed(() => isModrinthLinkedModpack.value || isImportedModpack.value),
isBusy: computed(
() =>
instance.value.install_stage !== 'installed' ||
@@ -173,7 +216,16 @@ provideInstallationSettings({
reinstalling.value ||
!!offline,
),
skipNonEssentialWarnings,
modpack: computed(() => {
if (isImportedModpack.value && instance.value.link?.type === 'imported_modpack') {
return {
iconUrl: instance.value.icon_path,
title: instance.value.link.name ?? instance.value.name,
versionNumber: instance.value.link.version_number ?? undefined,
filename: instance.value.link.filename ?? undefined,
}
}
if (!modpackInfo.value) return null
return {
iconUrl: modpackInfo.value.project.icon_url,
@@ -185,14 +237,14 @@ provideInstallationSettings({
currentPlatform: computed(() => instance.value.loader),
currentGameVersion: computed(() => instance.value.game_version),
currentLoaderVersion: computed(() => instance.value.loader_version ?? ''),
availablePlatforms: loaders?.value?.map((x) => x.name) ?? [],
availablePlatforms: computed(() => loadersQuery.data.value?.map((x) => x.name) ?? []),
resolveGameVersions(loader, showSnapshots) {
const versions = all_game_versions?.value ?? []
const versions = gameVersionsQuery.data.value ?? []
const filtered = versions.filter((item) => {
if (loader === 'vanilla') return true
const manifest = getManifest(loader)
return !!manifest?.value?.gameVersions?.some((x) => item.version === x.id)
return !!manifest?.gameVersions?.some((x) => item.version === x.id)
})
const result = (
showSnapshots ? filtered : filtered.filter((x) => x.version_type === 'release')
@@ -213,27 +265,41 @@ provideInstallationSettings({
return []
}
const manifest = getManifest(loader)
if (!manifest?.value) {
if (!manifest) {
debug('resolveLoaderVersions: no manifest', { loader, gameVersion })
return []
}
if (loader === 'fabric' || loader === 'quilt') {
const result = manifest.value.gameVersions[0]?.loaders ?? []
debug('resolveLoaderVersions: fabric/quilt result', {
const entry = manifest.gameVersions?.find((item) => item.id === gameVersion)
if (entry?.versionGroup) {
const result =
manifest.versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? []
debug('resolveLoaderVersions: version group result', {
loader,
gameVersion,
versionGroup: entry.versionGroup,
count: result.length,
})
return result
}
const placeholder = manifest.gameVersions?.find((item) => item.id === '${modrinth.gameVersion}')
if (placeholder) {
const result = manifest.gameVersions?.some((item) => item.id === gameVersion)
? placeholder.loaders
: []
debug('resolveLoaderVersions: placeholder result', {
loader,
gameVersion,
count: result.length,
})
return result
}
const result =
manifest.value.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? []
const result = entry?.loaders ?? []
debug('resolveLoaderVersions: result', { loader, gameVersion, count: result.length })
return result
},
resolveHasSnapshots(loader) {
const versions = all_game_versions?.value ?? []
const versions = gameVersionsQuery.data.value ?? []
if (loader === 'vanilla') {
const result = versions.some((x) => x.version_type !== 'release')
debug('resolveHasSnapshots: vanilla', { loader, result })
@@ -241,7 +307,7 @@ provideInstallationSettings({
}
const manifest = getManifest(loader)
const supported = versions.filter(
(item) => !!manifest?.value?.gameVersions?.some((x) => item.version === x.id),
(item) => !!manifest?.gameVersions?.some((x) => item.version === x.id),
)
const result = supported.some((x) => x.version_type !== 'release')
debug('resolveHasSnapshots:', {
@@ -255,25 +321,25 @@ provideInstallationSettings({
async save(platform, gameVersion, loaderVersionId) {
debug('save: called', {
instancePath: instance.value.path,
instanceId: instance.value.id,
platform,
gameVersion,
loaderVersionId,
})
const editProfile: Record<string, string | undefined> = {
const editInstancePatch: Record<string, string | undefined> = {
loader: platform,
game_version: gameVersion,
}
if (platform !== 'vanilla' && loaderVersionId) {
editProfile.loader_version = loaderVersionId
editInstancePatch.loader_version = loaderVersionId
}
await edit(instance.value.path, editProfile).catch(handleError)
debug('save: edit complete', { editProfile })
await edit(instance.value.id, editInstancePatch).catch(handleError)
debug('save: edit complete', { editInstancePatch })
},
afterSave: async () => {
debug('afterSave: installing', { instancePath: instance.value.path })
await install(instance.value.path, false).catch(handleError)
debug('afterSave: installing', { instanceId: instance.value.id })
await install_existing_instance(instance.value.id, false).catch(handleError)
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
@@ -282,9 +348,9 @@ provideInstallationSettings({
},
async repair() {
debug('repair: called', { instancePath: instance.value.path })
debug('repair: called', { instanceId: instance.value.id })
repairing.value = true
await install(instance.value.path, true).catch(handleError)
await install_existing_instance(instance.value.id, true).catch(handleError)
repairing.value = false
trackEvent('InstanceRepair', {
loader: instance.value.loader,
@@ -294,24 +360,52 @@ provideInstallationSettings({
},
async reinstallModpack() {
debug('reinstallModpack: called', { instancePath: instance.value.path })
debug('reinstallModpack: called', { instanceId: instance.value.id })
reinstalling.value = true
await update_repair_modrinth(instance.value.path).catch(handleError)
reinstalling.value = false
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
let shouldTrack = false
try {
if (isImportedModpack.value) {
shouldTrack = await installLocalModpackFromPicker()
} else {
await update_repair_modrinth(instance.value.id).catch(handleError)
shouldTrack = true
}
} finally {
reinstalling.value = false
}
if (shouldTrack) {
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
}
debug('reinstallModpack: done')
},
async swapModpack() {
debug('swapModpack: called', { instanceId: instance.value.id })
reinstalling.value = true
try {
const installed = await installLocalModpackFromPicker()
if (installed) {
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
}
} finally {
reinstalling.value = false
}
debug('swapModpack: done')
},
async unlinkModpack() {
debug('unlinkModpack: called', { instancePath: instance.value.path })
await edit(instance.value.path, {
linked_data: null as unknown as undefined,
debug('unlinkModpack: called', { instanceId: instance.value.id })
await edit(instance.value.id, {
link: null as unknown as undefined,
})
await queryClient.invalidateQueries({
queryKey: ['linkedModpackInfo', instance.value.path],
queryKey: ['linkedModpackInfo', instance.value.id],
})
onUnlinked()
debug('unlinkModpack: done')
@@ -320,11 +414,9 @@ provideInstallationSettings({
getCachedModpackVersions: () => null,
async fetchModpackVersions() {
debug('fetchModpackVersions: called', {
projectId: instance.value.linked_data?.project_id,
projectId: instance.value.link?.project_id,
})
const versions = await get_project_versions(instance.value.linked_data!.project_id!).catch(
handleError,
)
const versions = await get_project_versions(instance.value.link!.project_id!).catch(handleError)
debug('fetchModpackVersions: done', { count: versions?.length ?? 0 })
return (versions ?? []) as Labrinth.Versions.v2.Version[]
},
@@ -339,19 +431,18 @@ provideInstallationSettings({
async onModpackVersionConfirm(version) {
debug('onModpackVersionConfirm: called', {
versionId: version.id,
instancePath: instance.value.path,
instanceId: instance.value.id,
})
await update_managed_modrinth_version(instance.value.path, version.id)
await update_managed_modrinth_version(instance.value.id, version.id)
await queryClient.invalidateQueries({
queryKey: ['linkedModpackInfo', instance.value.path],
queryKey: ['linkedModpackInfo', instance.value.id],
})
debug('onModpackVersionConfirm: done')
},
updaterModalProps: computed(() => ({
isApp: true,
currentVersionId:
modpackInfo.value?.update_version_id ?? instance.value.linked_data?.version_id ?? '',
currentVersionId: modpackInfo.value?.update_version_id ?? instance.value.link?.version_id ?? '',
projectIconUrl: modpackInfo.value?.project?.icon_url,
projectName: modpackInfo.value?.project?.title ?? 'Modpack',
currentGameVersion: instance.value.game_version,
@@ -360,7 +451,10 @@ provideInstallationSettings({
isServer: false,
isApp: true,
showModpackVersionActions: !isMinecraftServer.value,
showModpackVersionActions: computed(
() => isModrinthLinkedModpack.value && !isMinecraftServer.value,
),
isLocalFile: isImportedModpack,
repairing,
reinstalling,
})
@@ -23,7 +23,7 @@ import { computed, readonly, ref, watch } from 'vue'
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
import useJavaTest from '@/composables/useJavaTest'
import useMemorySlider from '@/composables/useMemorySlider'
import { edit, get_optimal_jre_key } from '@/helpers/profile'
import { edit, get_optimal_jre_key } from '@/helpers/instance'
import { get } from '@/helpers/settings.ts'
import { injectInstanceSettings } from '@/providers/instance-settings'
@@ -36,7 +36,7 @@ const { instance } = injectInstanceSettings()
const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
const optimalJava = readonly(await get_optimal_jre_key(instance.value.path).catch(handleError))
const optimalJava = readonly(await get_optimal_jre_key(instance.value.id).catch(handleError))
const overrideJavaInstall = ref(!!instance.value.java_path)
const javaPath = ref(instance.value.java_path ?? optimalJava?.path ?? '')
@@ -104,7 +104,7 @@ const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) a
snapPoints: number[]
}
const editProfileObject = computed(() => {
const editInstanceObject = computed(() => {
return {
java_path:
overrideJavaInstall.value && javaPath.value
@@ -136,7 +136,7 @@ watch(
memory,
],
async () => {
await edit(instance.value.path, editProfileObject.value)
await edit(instance.value.id, editInstanceObject.value)
},
{ deep: true },
)
@@ -9,7 +9,7 @@ import {
} from '@modrinth/ui'
import { computed, type Ref, ref, watch } from 'vue'
import { edit } from '@/helpers/profile'
import { edit } from '@/helpers/instance'
import { get } from '@/helpers/settings.ts'
import { injectInstanceSettings } from '@/providers/instance-settings'
@@ -32,7 +32,7 @@ const fullscreenSetting: Ref<boolean> = ref(
instance.value.force_fullscreen ?? globalSettings.force_fullscreen,
)
const editProfileObject = computed(() => {
const editInstanceObject = computed(() => {
if (!overrideWindowSettings.value) {
return {
force_fullscreen: null,
@@ -48,7 +48,7 @@ const editProfileObject = computed(() => {
watch(
[overrideWindowSettings, resolution, fullscreenSetting],
async () => {
await edit(instance.value.path, editProfileObject.value)
await edit(instance.value.id, editInstanceObject.value)
},
{ deep: true },
)
@@ -14,7 +14,7 @@ defineProps<{
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : undefined"
size="24px"
:tint-by="instance.path"
:tint-by="instance.id"
/>
{{ instance.name }} <ChevronRightIcon />
</span>
@@ -16,7 +16,8 @@ import {
type TabbedModalTab,
useVIntl,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import type { PlatformTag } from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { convertFileSrc } from '@tauri-apps/api/core'
import { computed, nextTick, ref, watch } from 'vue'
@@ -26,12 +27,15 @@ import InstallationSettings from '@/components/ui/instance_settings/Installation
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
import { get_project_v3 } from '@/helpers/cache'
import { get_linked_modpack_info } from '@/helpers/profile'
import { get_linked_modpack_info } from '@/helpers/instance'
import { get_loader_versions } from '@/helpers/metadata'
import { get_game_versions, get_loaders } from '@/helpers/tags'
import { provideInstanceSettings } from '@/providers/instance-settings'
import type { GameInstance } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const queryClient = useQueryClient()
const props = defineProps<{
instance: GameInstance
@@ -45,7 +49,6 @@ const isMinecraftServer = ref(false)
const handleUnlinked = () => emit('unlinked')
const instanceRef = computed(() => props.instance)
const queryClient = useQueryClient()
const tabbedModal = ref<InstanceType<typeof TabbedModal> | null>(null)
function hide() {
@@ -64,8 +67,8 @@ watch(
() => props.instance,
(instance) => {
isMinecraftServer.value = false
if (instance.linked_data?.project_id) {
get_project_v3(instance.linked_data.project_id, 'must_revalidate')
if (instance.link?.project_id) {
get_project_v3(instance.link.project_id, 'must_revalidate')
.then((project: Labrinth.Projects.v3.Project | undefined) => {
if (project?.minecraft_server != null) {
isMinecraftServer.value = true
@@ -120,11 +123,50 @@ const tabs = computed<TabbedModalTab[]>(() => [
},
])
function getSupportedModpackLoaders() {
return get_loaders().then((value: PlatformTag[]) =>
value
.filter((item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla')
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
)
}
// Preload
useQuery({
queryKey: ['instance-settings', 'loader-versions', 'fabric'],
queryFn: () => get_loader_versions('fabric'),
})
useQuery({
queryKey: ['instance-settings', 'loader-versions', 'forge'],
queryFn: () => get_loader_versions('forge'),
})
useQuery({
queryKey: ['instance-settings', 'loader-versions', 'quilt'],
queryFn: () => get_loader_versions('quilt'),
})
useQuery({
queryKey: ['instance-settings', 'loader-versions', 'neo'],
queryFn: () => get_loader_versions('neo'),
})
useQuery({
queryKey: ['instance-settings', 'game-versions'],
queryFn: get_game_versions,
})
useQuery({
queryKey: ['instance-settings', 'loaders', 'modpack'],
queryFn: getSupportedModpackLoaders,
})
useQuery({
queryKey: computed(() => ['linkedModpackInfo', props.instance.id]),
queryFn: () => get_linked_modpack_info(props.instance.id, 'stale_while_revalidate'),
enabled: computed(() => !!props.instance.link?.project_id && !props.offline),
})
function show(tabIndex?: number) {
if (props.instance.linked_data?.project_id) {
if (props.instance.link?.project_id) {
queryClient.prefetchQuery({
queryKey: ['linkedModpackInfo', props.instance.path],
queryFn: () => get_linked_modpack_info(props.instance.path, 'stale_while_revalidate'),
queryKey: ['linkedModpackInfo', props.instance.id],
queryFn: () => get_linked_modpack_info(props.instance.id, 'stale_while_revalidate'),
})
}
tabbedModal.value?.show()
@@ -147,7 +189,7 @@ defineExpose({ show, hide })
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : undefined"
size="24px"
:tint-by="props.instance.path"
:tint-by="props.instance.id"
/>
{{ instance.name }} <ChevronRightIcon />
<span class="font-extrabold text-contrast">{{
@@ -68,17 +68,17 @@ const messages = defineMessages({
})
const emit = defineEmits<{
(e: 'go-to-instance', instancePath: string): void
(e: 'go-to-instance', instanceId: string): void
(e: 'create-anyway'): void
}>()
const modal = ref<InstanceType<typeof NewModal>>()
const instanceName = ref('')
const instancePath = ref('')
const instanceId = ref('')
function show(name: string, path: string) {
function show(name: string, id: string) {
instanceName.value = name
instancePath.value = path
instanceId.value = id
modal.value?.show()
}
@@ -88,7 +88,7 @@ function handleCancel() {
function handleGoToInstance() {
modal.value?.hide()
emit('go-to-instance', instancePath.value)
emit('go-to-instance', instanceId.value)
}
function handleCreateAnyway() {
@@ -31,7 +31,8 @@ import dayjs from 'dayjs'
import { computed, ref, watch } from 'vue'
import { get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import { update_managed_modrinth_version } from '@/helpers/profile'
import { wait_for_install_job } from '@/helpers/install'
import { update_managed_modrinth_version } from '@/helpers/instance'
import type { GameInstance } from '@/helpers/types'
import { injectServerInstall } from '@/providers/server-install'
@@ -202,15 +203,15 @@ async function computeDependencyDiffs(
}
async function checkUpdateAvailable(inst: GameInstance): Promise<DependencyDiff[] | null> {
if (!inst.linked_data) return null
if (!modpackVersionId.value || !inst.linked_data.version_id) return null
if (!inst.link) return null
if (!modpackVersionId.value || !inst.link.version_id) return null
try {
// For server projects, linked_data.project_id is the server project but
// linked_data.version_id references a content modpack version from a different project.
// Detect this by comparing the version's project_id with linked_data.project_id.
// For server projects, link.project_id is the server project but
// link.version_id references a content modpack version from a different project.
// Detect this by comparing the version's project_id with link.project_id.
modpackVersion.value = await get_version(modpackVersionId.value, 'bypass')
const instanceModpackVersion = await get_version(inst.linked_data.version_id, 'bypass')
const instanceModpackVersion = await get_version(inst.link.version_id, 'bypass')
// Compute dependency diffs between current and latest version
if (instanceModpackVersion && modpackVersion.value) {
@@ -238,11 +239,12 @@ watch(
async function handleUpdate() {
hide()
const serverProjectId = instance.value?.linked_data?.project_id
const serverProjectId = instance.value?.link?.project_id
if (serverProjectId) startInstallingServer(serverProjectId)
try {
if (modpackVersionId.value && instance.value) {
await update_managed_modrinth_version(instance.value.path, modpackVersionId.value)
const job = await update_managed_modrinth_version(instance.value.id, modpackVersionId.value)
await wait_for_install_job(job.job_id)
await onUpdateComplete.value()
}
} catch (error) {
@@ -253,10 +255,8 @@ async function handleUpdate() {
}
function handleReport() {
if (instance.value?.linked_data?.project_id) {
openUrl(
`https://modrinth.com/report?item=project&itemID=${instance.value.linked_data.project_id}`,
)
if (instance.value?.link?.project_id) {
openUrl(`https://modrinth.com/report?item=project&itemID=${instance.value.link.project_id}`)
}
}
@@ -297,11 +297,8 @@ const messages = defineMessages({
})
const hasUpdate = computed(() => {
if (!instance.value?.linked_data) return false
return (
modpackVersionId.value != null &&
modpackVersionId.value !== instance.value.linked_data.version_id
)
if (!instance.value?.link) return false
return modpackVersionId.value != null && modpackVersionId.value !== instance.value.link.version_id
})
defineExpose({ show, hide, hasUpdate })
@@ -11,6 +11,7 @@ const themeStore = useTheming()
const { formatMessage } = useVIntl()
const worldsInHomeFlag: FeatureFlag = 'worlds_in_home'
const skipNonEssentialWarningsFlag: FeatureFlag = 'skip_non_essential_warnings'
const skipUnknownPackWarningFlag: FeatureFlag = 'skip_unknown_pack_warning'
const showPlayTimeFlag: FeatureFlag = 'show_instance_play_time'
@@ -101,6 +102,15 @@ const messages = defineMessages({
defaultMessage:
"If you attempt to install a Modrinth Pack file (.mrpack) that isn't hosted on Modrinth, we'll make sure you understand the risks before installing it.",
},
skipNonEssentialWarningsTitle: {
id: 'app.appearance-settings.skip-non-essential-warnings.title',
defaultMessage: 'Skip non-essential warnings',
},
skipNonEssentialWarningsDescription: {
id: 'app.appearance-settings.skip-non-essential-warnings.description',
defaultMessage:
'Automatically skips low-risk confirmations like duplicate modpack installs, normal content deletion, bulk updates, unlinking modpacks, and repair prompts. Dangerous warnings will still be shown.',
},
showPlayTimeTitle: {
id: 'app.appearance-settings.show-play-time.title',
defaultMessage: 'Show play time',
@@ -285,6 +295,25 @@ watch(
/>
</div>
<div class="mt-6 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">
{{ formatMessage(messages.skipNonEssentialWarningsTitle) }}
</h2>
<p class="m-0 mt-1">{{ formatMessage(messages.skipNonEssentialWarningsDescription) }}</p>
</div>
<Toggle
:model-value="themeStore.getFeatureFlag(skipNonEssentialWarningsFlag)"
@update:model-value="
() => {
const newValue = !themeStore.getFeatureFlag(skipNonEssentialWarningsFlag)
themeStore.featureFlags[skipNonEssentialWarningsFlag] = newValue
settings.feature_flags[skipNonEssentialWarningsFlag] = newValue
}
"
/>
</div>
<div class="mt-6 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">
@@ -1,5 +1,5 @@
<script setup>
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import { BoxIcon, FolderOpenIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, Slider, StyledInput } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog'
import { ref, watch } from 'vue'
@@ -7,9 +7,13 @@ import { ref, watch } from 'vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { purge_cache_types } from '@/helpers/cache.js'
import { get, set } from '@/helpers/settings.ts'
import { showAppDbBackupsFolder } from '@/helpers/utils.js'
import { useTheming } from '@/store/state'
const { handleError } = injectNotificationManager()
const themeStore = useTheming()
const settings = ref(await get())
const purgeCacheConfirmModal = ref(null)
watch(
settings,
@@ -48,6 +52,19 @@ async function purgeCache() {
]).catch(handleError)
}
function handlePurgeCacheClick() {
if (themeStore.getFeatureFlag('skip_non_essential_warnings')) {
void purgeCache()
return
}
purgeCacheConfirmModal.value?.show()
}
async function openDbBackupsFolder() {
await showAppDbBackupsFolder().catch(handleError)
}
async function findLauncherDir() {
const newDir = await open({
multiple: false,
@@ -97,7 +114,7 @@ async function findLauncherDir() {
@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()">
<button id="purge-cache" class="btn min-w-max" @click="handlePurgeCacheClick">
<TrashIcon />
Purge cache
</button>
@@ -136,5 +153,16 @@ async function findLauncherDir() {
value if you are frequently getting I/O errors. (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">App database backups</h2>
<button id="open-db-backups-folder" class="btn min-w-max" @click="openDbBackupsFolder">
<FolderOpenIcon />
Open backups folder
</button>
<p class="m-0 leading-tight text-secondary">
Backups of important app data are stored here in case you need to recover them later.
</p>
</div>
</div>
</template>
@@ -27,10 +27,10 @@ import { useRouter } from 'vue-router'
import { trackEvent } from '@/helpers/analytics'
import { get_project } from '@/helpers/cache'
import { process_listener } from '@/helpers/events'
import { get_by_profile_path } from '@/helpers/process'
import { kill, run } from '@/helpers/profile'
import { kill, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { showProfileInFolder } from '@/helpers/utils'
import { showInstanceInFolder } from '@/helpers/utils'
import { handleSevereError } from '@/store/error'
const { handleError } = injectNotificationManager()
@@ -52,13 +52,13 @@ const props = defineProps<{
last_played: Dayjs
}>()
const loadingModpack = ref(!!props.instance.linked_data)
const loadingModpack = ref(!!props.instance.link)
const modpack = ref()
if (props.instance.linked_data) {
if (props.instance.link) {
nextTick().then(async () => {
modpack.value = await get_project(props.instance.linked_data?.project_id, 'must_revalidate')
modpack.value = await get_project(props.instance.link?.project_id, 'must_revalidate')
loadingModpack.value = false
})
}
@@ -81,8 +81,8 @@ const playing = ref(false)
const play = async (event: MouseEvent) => {
event?.stopPropagation()
loading.value = true
await run(props.instance.path)
.catch((err) => handleSevereError(err, { profilePath: props.instance.path }))
await run(props.instance.id)
.catch((err) => handleSevereError(err, { instanceId: props.instance.id }))
.finally(() => {
trackEvent('InstanceStart', {
loader: props.instance.loader,
@@ -97,7 +97,7 @@ const play = async (event: MouseEvent) => {
const stop = async (event: MouseEvent) => {
event?.stopPropagation()
loading.value = true
await kill(props.instance.path).catch(handleError)
await kill(props.instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: props.instance.loader,
game_version: props.instance.game_version,
@@ -112,7 +112,7 @@ const unlistenProcesses = await process_listener(async () => {
})
const checkProcess = async () => {
const runningProcesses = await get_by_profile_path(props.instance.path).catch(handleError)
const runningProcesses = await get_by_instance_id(props.instance.id).catch(handleError)
playing.value = runningProcesses.length > 0
}
@@ -130,7 +130,7 @@ onUnmounted(() => {
<template #clickable>
<router-link
class="no-click-animation"
:to="`/instance/${encodeURIComponent(instance.path)}`"
:to="`/instance/${encodeURIComponent(instance.id)}`"
/>
</template>
<div
@@ -138,7 +138,7 @@ onUnmounted(() => {
>
<Avatar
:src="instanceIcon ? convertFileSrc(instanceIcon) : undefined"
:tint-by="instance.path"
:tint-by="instance.id"
size="48px"
/>
<div class="flex flex-col col-span-2 justify-between h-full">
@@ -206,12 +206,12 @@ onUnmounted(() => {
:options="[
{
id: 'open-instance',
shown: !!instance.path,
action: () => router.push(encodeURI(`/instance/${instance.path}`)),
shown: !!instance.id,
action: () => router.push(encodeURI(`/instance/${instance.id}`)),
},
{
id: 'open-folder',
action: () => showProfileInFolder(instance.path),
action: () => showInstanceInFolder(instance.id),
},
]"
>
@@ -10,13 +10,13 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { trackEvent } from '@/helpers/analytics'
import { process_listener, profile_listener } from '@/helpers/events'
import { instance_listener, process_listener } from '@/helpers/events'
import { kill, run } from '@/helpers/instance'
import { get_all } from '@/helpers/process'
import { kill, run } from '@/helpers/profile'
import { get_game_versions } from '@/helpers/tags'
import type { GameInstance } from '@/helpers/types'
import {
get_profile_protocol_version,
get_instance_protocol_version,
get_recent_worlds,
getWorldIdentifier,
hasServerQuickPlaySupport,
@@ -27,7 +27,7 @@ import {
type ServerWorld,
start_join_server,
start_join_singleplayer_world,
type WorldWithProfile,
type WorldWithInstance,
} from '@/helpers/worlds.ts'
import { handleSevereError } from '@/store/error'
import { useTheming } from '@/store/theme.ts'
@@ -66,7 +66,7 @@ type InstanceJumpBackInItem = BaseJumpBackInItem & {
type WorldJumpBackInItem = BaseJumpBackInItem & {
type: 'world'
world: WorldWithProfile
world: WorldWithInstance
}
type JumpBackInItem = InstanceJumpBackInItem | WorldJumpBackInItem
@@ -100,7 +100,7 @@ async function populateJumpBackIn() {
const worlds = await get_recent_worlds(MAX_JUMP_BACK_IN, ['normal', 'favorite'])
worlds.forEach((world) => {
const instance = props.recentInstances.find((instance) => instance.path === world.profile)
const instance = props.recentInstances.find((instance) => instance.id === world.instance_id)
if (!instance || !world.last_played) {
return
@@ -115,23 +115,23 @@ async function populateJumpBackIn() {
})
const servers: {
instancePath: string
instanceId: string
address: string
}[] = worldItems
.filter((item) => item.world.type === 'server' && item.instance)
.map((item) => ({
instancePath: item.instance.path,
instanceId: item.instance.id,
address: (item.world as ServerWorld).address,
}))
// fetch protocol versions for all unique MC versions with server worlds
const uniqueServerInstances = new Set<string>(servers.map((x) => x.instancePath))
const uniqueServerInstances = new Set<string>(servers.map((x) => x.instanceId))
await Promise.all(
[...uniqueServerInstances].map((path) =>
get_profile_protocol_version(path)
.then((protoVer) => (protocolVersions.value[path] = protoVer))
[...uniqueServerInstances].map((instanceId) =>
get_instance_protocol_version(instanceId)
.then((protoVer) => (protocolVersions.value[instanceId] = protoVer))
.catch(() => {
console.error(`Failed to get profile protocol for: ${path} `)
console.error(`Failed to get instance protocol for: ${instanceId} `)
}),
),
)
@@ -145,14 +145,14 @@ async function populateJumpBackIn() {
}
})
servers.forEach(({ instancePath, address }) =>
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address),
servers.forEach(({ instanceId, address }) =>
refreshServerData(serverData.value[address], protocolVersions.value[instanceId], address),
)
}
const instanceItems: InstanceJumpBackInItem[] = []
for (const instance of props.recentInstances) {
const worldItem = worldItems.find((item) => item.instance.path === instance.path)
const worldItem = worldItems.find((item) => item.instance.id === instance.id)
if ((worldItem && worldItem.last_played.isAfter(TWO_WEEKS_AGO)) || !instance.last_played) {
continue
}
@@ -171,14 +171,14 @@ async function populateJumpBackIn() {
.slice(0, MAX_JUMP_BACK_IN)
}
function refreshServer(address: string, instancePath: string) {
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
function refreshServer(address: string, instanceId: string) {
refreshServerData(serverData.value[address], protocolVersions.value[instanceId], address)
}
async function joinWorld(world: WorldWithProfile, instance?: GameInstance) {
async function joinWorld(world: WorldWithInstance, instance?: GameInstance) {
console.log(`Joining world ${getWorldIdentifier(world)}`)
if (world.type === 'server') {
await start_join_server(world.profile, world.address).catch(handleError)
await start_join_server(world.instance_id, world.address).catch(handleError)
if (instance) {
trackEvent('InstanceStart', {
loader: instance.loader,
@@ -187,13 +187,13 @@ async function joinWorld(world: WorldWithProfile, instance?: GameInstance) {
})
}
} else if (world.type === 'singleplayer') {
await start_join_singleplayer_world(world.profile, world.path).catch(handleError)
await start_join_singleplayer_world(world.instance_id, world.path).catch(handleError)
}
}
async function playInstance(instance: GameInstance) {
await run(instance.path)
.catch((err) => handleSevereError(err, { profilePath: instance.path }))
await run(instance.id)
.catch((err) => handleSevereError(err, { instanceId: instance.id }))
.finally(() => {
trackEvent('InstanceStart', {
loader: instance.loader,
@@ -210,14 +210,14 @@ async function stopInstance(path: string) {
})
}
const currentProfile = ref<string>()
const currentInstance = ref<string>()
const currentWorld = ref<string>()
const unlistenProcesses = await process_listener(async () => {
await checkProcesses()
})
const unlistenProfiles = await profile_listener(async () => {
const unlistenInstances = await instance_listener(async () => {
await populateJumpBackIn().catch(() => {
console.error('Failed to populate jump back in')
})
@@ -227,18 +227,18 @@ const runningInstances = ref<string[]>([])
type ProcessMetadata = {
uuid: string
profile_path: string
instance_id: string
start_time: string
}
const checkProcesses = async () => {
const runningProcesses: ProcessMetadata[] = await get_all().catch(handleError)
const runningPaths = runningProcesses.map((x) => x.profile_path)
const runningPaths = runningProcesses.map((x) => x.instance_id)
const stoppedInstances = runningInstances.value.filter((x) => !runningPaths.includes(x))
if (currentProfile.value && stoppedInstances.includes(currentProfile.value)) {
currentProfile.value = undefined
if (currentInstance.value && stoppedInstances.includes(currentInstance.value)) {
currentInstance.value = undefined
currentWorld.value = undefined
}
@@ -252,7 +252,7 @@ onMounted(() => {
onUnmounted(() => {
unlistenProcesses()
unlistenProfiles()
unlistenInstances()
})
</script>
@@ -278,14 +278,14 @@ onUnmounted(() => {
<div class="grid-when-huge flex flex-col w-full gap-2">
<template
v-for="item in jumpBackInItems"
:key="`${item.instance.path}-${item.type === 'world' ? getWorldIdentifier(item.world) : 'instance'}`"
:key="`${item.instance.id}-${item.type === 'world' ? getWorldIdentifier(item.world) : 'instance'}`"
>
<WorldItem
v-if="item.type === 'world'"
:world="item.world"
:playing-instance="runningInstances.includes(item.instance.path)"
:playing-instance="runningInstances.includes(item.instance.id)"
:playing-world="
currentProfile === item.instance.path && currentWorld === getWorldIdentifier(item.world)
currentInstance === item.instance.id && currentWorld === getWorldIdentifier(item.world)
"
:refreshing="
item.world.type === 'server'
@@ -306,34 +306,34 @@ onUnmounted(() => {
:rendered-motd="
item.world.type === 'server' ? serverData[item.world.address].renderedMotd : undefined
"
:current-protocol="protocolVersions[item.instance.path]"
:current-protocol="protocolVersions[item.instance.id]"
:game-mode="
item.world.type === 'singleplayer' ? GAME_MODES[item.world.game_mode] : undefined
"
:instance-path="item.instance.path"
:instance-id="item.instance.id"
:instance-name="item.instance.name"
:instance-icon="item.instance.icon_path"
@refresh="
() =>
item.world.type === 'server'
? refreshServer(item.world.address, item.instance.path)
? refreshServer(item.world.address, item.instance.id)
: {}
"
@update="() => populateJumpBackIn()"
@play="
() => {
currentProfile = item.instance.path
currentInstance = item.instance.id
currentWorld = getWorldIdentifier(item.world)
joinWorld(item.world, item.instance)
}
"
@play-instance="
() => {
currentProfile = item.instance.path
currentInstance = item.instance.id
playInstance(item.instance)
}
"
@stop="() => stopInstance(item.instance.path)"
@stop="() => stopInstance(item.instance.id)"
/>
<InstanceItem v-else :instance="item.instance" :last_played="item.last_played" />
</template>
@@ -2,6 +2,7 @@
import {
ClipboardCopyIcon,
EditIcon,
ExternalIcon,
EyeIcon,
FolderOpenIcon,
IssuesIcon,
@@ -23,6 +24,7 @@ import {
ButtonStyled,
commonMessages,
defineMessages,
injectNotificationManager,
OverflowMenu,
SmartClickable,
TagItem,
@@ -39,7 +41,7 @@ import type { Component } from 'vue'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { copyToClipboard } from '@/helpers/utils'
import { copyToClipboard, createInstanceShortcut } from '@/helpers/utils'
import type {
ProtocolVersion,
ServerStatus,
@@ -60,6 +62,7 @@ const formatDateTime = useFormatDateTime({
})
const router = useRouter()
const { addNotification } = injectNotificationManager()
const emit = defineEmits<{
(e: 'play' | 'play-instance' | 'update' | 'stop' | 'refresh' | 'edit' | 'delete'): void
@@ -91,9 +94,10 @@ const props = withDefaults(
managed?: boolean
// Instance
instancePath?: string
instanceId?: string
instanceName?: string
instanceIcon?: string
shortcutInstanceId?: string
}>(),
{
playingInstance: false,
@@ -110,9 +114,10 @@ const props = withDefaults(
gameMode: undefined,
managed: false,
instancePath: undefined,
instanceId: undefined,
instanceName: undefined,
instanceIcon: undefined,
shortcutInstanceId: undefined,
},
)
@@ -131,6 +136,33 @@ const serverIncompatible = computed(
const locked = computed(() => props.world.type === 'singleplayer' && props.world.locked)
const managed = computed(() => props.managed)
const shortcutInstanceId = computed(() => props.shortcutInstanceId ?? props.instanceId)
async function createShortcut() {
if (!shortcutInstanceId.value) return
try {
const shortcutPath = await createInstanceShortcut(
props.world.name,
shortcutInstanceId.value,
props.world.type === 'server'
? { server: (props.world as ServerWorld).address }
: { singleplayerWorld: (props.world as SingleplayerWorld).path },
)
if (!shortcutPath) return
addNotification({
type: 'success',
title: 'Shortcut created',
})
} catch (error) {
addNotification({
type: 'error',
title: 'Failed to create shortcut',
text: error instanceof Error ? error.message : '',
})
}
}
const messages = defineMessages({
hardcore: {
@@ -185,6 +217,10 @@ const messages = defineMessages({
id: 'instance.worlds.dont_show_on_home',
defaultMessage: `Don't show on Home`,
},
createShortcut: {
id: 'instance.worlds.create_shortcut',
defaultMessage: 'Create shortcut',
},
linkedServer: {
id: 'instance.worlds.linked_server',
defaultMessage: 'Managed by server project',
@@ -209,10 +245,10 @@ const messages = defineMessages({
</script>
<template>
<SmartClickable>
<template v-if="instancePath" #clickable>
<template v-if="instanceId" #clickable>
<router-link
class="no-click-animation"
:to="`/instance/${encodeURIComponent(instancePath)}/worlds?highlight=${encodeURIComponent(getWorldIdentifier(world))}`"
:to="`/instance/${encodeURIComponent(instanceId)}/worlds?highlight=${encodeURIComponent(getWorldIdentifier(world))}`"
/>
</template>
<div
@@ -324,16 +360,16 @@ const messages = defineMessages({
</template>
<template v-else> {{ formatMessage(messages.notPlayedYet) }} </template>
</div>
<template v-if="instancePath">
<template v-if="instanceId">
<router-link
class="flex items-center gap-1 truncate hover:underline text-secondary smart-clickable:allow-pointer-events"
:to="`/instance/${instancePath}`"
:to="`/instance/${instanceId}`"
>
<Avatar
:src="instanceIcon ? convertFileSrc(instanceIcon) : undefined"
size="16px"
:tint-by="instancePath"
:tint-by="instanceId"
class="shrink-0"
/>
<span class="truncate">{{ instanceName }}</span>
@@ -420,14 +456,14 @@ const messages = defineMessages({
:options="[
{
id: 'play-instance',
shown: !!instancePath,
shown: !!instanceId,
disabled: playingInstance,
action: () => emit('play-instance'),
},
{
id: 'open-instance',
shown: !!instancePath,
action: () => router.push(encodeURI(`/instance/${instancePath}`)),
shown: !!instanceId,
action: () => router.push(`/instance/${encodeURIComponent(instanceId)}`),
},
{
id: 'refresh',
@@ -442,7 +478,7 @@ const messages = defineMessages({
{
id: 'edit',
action: () => emit('edit'),
shown: !instancePath,
shown: !instanceId,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
@@ -457,14 +493,14 @@ const messages = defineMessages({
},
{
divider: true,
shown: !!instancePath,
shown: !!instanceId,
},
{
id: 'dont-show-on-home',
shown: !!instancePath,
shown: !!instanceId,
action: () => {
set_world_display_status(
instancePath,
instanceId,
world.type,
getWorldIdentifier(world),
'hidden',
@@ -473,16 +509,21 @@ const messages = defineMessages({
})
},
},
{
id: 'create-shortcut',
shown: !!shortcutInstanceId,
action: () => createShortcut(),
},
{
divider: true,
shown: !instancePath,
shown: !instanceId,
},
{
id: 'delete',
color: 'red',
hoverFilled: true,
action: () => emit('delete'),
shown: !instancePath,
shown: !instanceId,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
@@ -517,6 +558,10 @@ const messages = defineMessages({
<UpdatedIcon aria-hidden="true" />
{{ formatMessage(commonMessages.refreshButton) }}
</template>
<template #create-shortcut>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(messages.createShortcut) }}
</template>
<template #dont-show-on-home>
<XIcon aria-hidden="true" />
{{ formatMessage(messages.dontShowOnHome) }}
@@ -12,7 +12,11 @@ import { ref } from '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'
import {
add_server_to_instance,
type ServerPackStatus,
type ServerWorld,
} from '@/helpers/worlds.ts'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -35,8 +39,8 @@ async function addServer(play: boolean) {
const serverName = name.value ? name.value : address.value
const resourcePackStatus = resourcePack.value
const index =
(await add_server_to_profile(
props.instance.path,
(await add_server_to_instance(
props.instance.id,
serverName,
address.value,
resourcePackStatus,
@@ -0,0 +1,124 @@
<script setup lang="ts">
import { TrashIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
commonMessages,
defineMessages,
NewModal,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
import type { World } from '@/helpers/worlds.ts'
const { formatMessage } = useVIntl()
const props = defineProps<{
world: World | null
}>()
const emit = defineEmits<{
confirm: [world: World]
}>()
const messages = defineMessages({
removeServerTitle: {
id: 'app.instance.worlds.remove-server-modal.title',
defaultMessage: 'Remove server',
},
deleteWorldTitle: {
id: 'app.instance.worlds.delete-world-modal.title',
defaultMessage: 'Delete world',
},
removeServerWarningHeader: {
id: 'app.instance.worlds.remove-server-modal.warning-header',
defaultMessage: 'Removing {name}',
},
deleteWorldWarningHeader: {
id: 'app.instance.worlds.delete-world-modal.warning-header',
defaultMessage: 'Deleting {name}',
},
removeServerWarningBody: {
id: 'app.instance.worlds.remove-server-modal.warning-body',
defaultMessage:
'This server will be removed from your server list and from the in-game server list. You can add it again later if you know the address.',
},
deleteWorldWarningBody: {
id: 'app.instance.worlds.delete-world-modal.warning-body',
defaultMessage:
'This world will be permanently deleted from this instance. This action cannot be undone.',
},
removeServerButton: {
id: 'app.instance.worlds.remove-server-modal.remove-button',
defaultMessage: 'Remove server',
},
deleteWorldButton: {
id: 'app.instance.worlds.delete-world-modal.delete-button',
defaultMessage: 'Delete world',
},
})
const modal = ref<InstanceType<typeof NewModal>>()
const isServer = computed(() => props.world?.type === 'server')
const isSingleplayer = computed(() => props.world?.type === 'singleplayer')
const titleMessage = computed(() =>
isServer.value ? messages.removeServerTitle : messages.deleteWorldTitle,
)
const actionMessage = computed(() =>
isServer.value ? messages.removeServerButton : messages.deleteWorldButton,
)
const warningHeaderMessage = computed(() =>
isServer.value ? messages.removeServerWarningHeader : messages.deleteWorldWarningHeader,
)
const warningBodyMessage = computed(() =>
isServer.value ? messages.removeServerWarningBody : messages.deleteWorldWarningBody,
)
function show() {
modal.value?.show()
}
function hide() {
modal.value?.hide()
}
function confirm() {
if (!props.world) return
emit('confirm', props.world)
hide()
}
defineExpose({ show, hide })
</script>
<template>
<NewModal ref="modal" :header="formatMessage(titleMessage)" fade="danger" max-width="500px">
<div class="flex flex-col gap-4">
<Admonition
type="critical"
:header="formatMessage(warningHeaderMessage, { name: world?.name })"
>
{{ formatMessage(warningBodyMessage) }}
</Admonition>
</div>
<template #actions>
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="hide">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button :disabled="!isServer && !isSingleplayer" @click="confirm">
<TrashIcon />
{{ formatMessage(actionMessage) }}
</button>
</ButtonStyled>
</div>
</template>
</NewModal>
</template>
@@ -15,7 +15,7 @@ import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types'
import {
type DisplayStatus,
edit_server_in_profile,
edit_server_in_instance,
type ServerPackStatus,
type ServerWorld,
set_world_display_status,
@@ -46,8 +46,8 @@ const newDisplayStatus = computed(() => (hideFromHome.value ? 'hidden' : 'normal
async function saveServer() {
const serverName = name.value ? name.value : address.value
const resourcePackStatus = resourcePack.value
await edit_server_in_profile(
props.instance.path,
await edit_server_in_instance(
props.instance.id,
index.value,
serverName,
address.value,
@@ -56,7 +56,7 @@ async function saveServer() {
if (newDisplayStatus.value !== displayStatus.value) {
await set_world_display_status(
props.instance.path,
props.instance.id,
'server',
address.value,
newDisplayStatus.value,
@@ -40,14 +40,14 @@ const hideFromHome = ref(false)
const newDisplayStatus = computed(() => (hideFromHome.value ? 'hidden' : 'normal'))
async function saveWorld() {
await rename_world(props.instance.path, path.value, name.value).catch(handleError)
await rename_world(props.instance.id, path.value, name.value).catch(handleError)
if (removeIcon.value) {
await reset_world_icon(props.instance.path, path.value).catch(handleError)
await reset_world_icon(props.instance.id, path.value).catch(handleError)
}
if (newDisplayStatus.value !== displayStatus.value) {
await set_world_display_status(
props.instance.path,
props.instance.id,
'singleplayer',
path.value,
newDisplayStatus.value,
@@ -0,0 +1,745 @@
import { CheckIcon, CopyIcon, UpdatedIcon } from '@modrinth/assets'
import {
defineMessages,
type PopupNotificationButton,
type PopupNotificationProgressItem,
type PopupNotificationProgressType,
useVIntl,
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { computed, ref } from 'vue'
import type { Router } from 'vue-router'
import { install_job_listener } from '@/helpers/events'
import {
install_job_dismiss,
install_job_list,
install_job_retry,
install_job_support_details,
installJobInstanceId,
type InstallJobSnapshot,
type InstallJobStatus,
type InstallPhaseId,
type InstallProgress,
} from '@/helpers/install'
import { get_many as getInstances } from '@/helpers/instance'
const messages = defineMessages({
installs: {
id: 'app.action-bar.installs',
defaultMessage: 'Installs',
},
retry: {
id: 'app.action-bar.install.retry',
defaultMessage: 'Retry',
},
copyDetails: {
id: 'app.action-bar.install.copy-details',
defaultMessage: 'Copy details',
},
copied: {
id: 'app.action-bar.install.copied-details',
defaultMessage: 'Copied',
},
dismiss: {
id: 'app.action-bar.install.dismiss',
defaultMessage: 'Dismiss',
},
openInstance: {
id: 'app.action-bar.install.open-instance',
defaultMessage: 'Open instance',
},
unknownInstance: {
id: 'app.action-bar.install.unknown-instance',
defaultMessage: 'Unknown instance',
},
})
const phaseMessages = defineMessages({
preparing_instance: {
id: 'app.install.phase.preparing_instance',
defaultMessage: 'Queued to install',
},
resolving_pack: {
id: 'app.install.phase.resolving_pack',
defaultMessage: 'Resolving content',
},
downloading_pack_file: {
id: 'app.install.phase.downloading_pack_file',
defaultMessage: 'Downloading pack file',
},
reading_pack_manifest: {
id: 'app.install.phase.reading_pack_manifest',
defaultMessage: 'Reading pack manifest',
},
downloading_content: {
id: 'app.install.phase.downloading_content',
defaultMessage: 'Downloading content',
},
extracting_overrides: {
id: 'app.install.phase.extracting_overrides',
defaultMessage: 'Extracting overrides',
},
resolving_minecraft: {
id: 'app.install.phase.resolving_minecraft',
defaultMessage: 'Resolving Minecraft',
},
resolving_loader: {
id: 'app.install.phase.resolving_loader',
defaultMessage: 'Resolving loader',
},
preparing_java: {
id: 'app.install.phase.preparing_java',
defaultMessage: 'Preparing Java',
},
downloading_minecraft: {
id: 'app.install.phase.downloading_minecraft',
defaultMessage: 'Downloading Minecraft',
},
running_loader_processors: {
id: 'app.install.phase.running_loader_processors',
defaultMessage: 'Running loader processors',
},
finalizing: {
id: 'app.install.phase.finalizing',
defaultMessage: 'Finalizing',
},
rolling_back: {
id: 'app.install.phase.rolling_back',
defaultMessage: 'Rolling back',
},
})
const javaStepMessages = defineMessages({
resolving: {
id: 'app.install.phase.preparing_java.resolving',
defaultMessage: 'Preparing Java {version}',
},
fetching_metadata: {
id: 'app.install.phase.preparing_java.fetching-metadata',
defaultMessage: 'Fetching Java {version}',
},
downloading: {
id: 'app.install.phase.preparing_java.downloading',
defaultMessage: 'Downloading Java {version}',
},
extracting: {
id: 'app.install.phase.preparing_java.extracting',
defaultMessage: 'Extracting Java {version}',
},
validating: {
id: 'app.install.phase.preparing_java.validating',
defaultMessage: 'Validating Java {version}',
},
})
const failureSummaryMessages = defineMessages({
canceled: {
id: 'app.action-bar.install.summary.canceled',
defaultMessage: 'Canceled',
},
appClosed: {
id: 'app.action-bar.install.summary.app-closing',
defaultMessage: 'Canceled due to app closing',
},
downloadFailed: {
id: 'app.action-bar.install.summary.download-failed',
defaultMessage: "Download couldn't finish",
},
modrinthUnreachable: {
id: 'app.action-bar.install.summary.modrinth-unreachable',
defaultMessage: "Couldn't reach Modrinth",
},
packDownloadFailed: {
id: 'app.action-bar.install.summary.pack-download-failed',
defaultMessage: "Couldn't download pack",
},
badModpackFile: {
id: 'app.action-bar.install.summary.bad-modpack-file',
defaultMessage: "Couldn't read modpack",
},
invalidModpack: {
id: 'app.action-bar.install.summary.invalid-modpack',
defaultMessage: 'Modpack data invalid',
},
contentDownloadFailed: {
id: 'app.action-bar.install.summary.content-download-failed',
defaultMessage: "Couldn't download files",
},
corruptDownload: {
id: 'app.action-bar.install.summary.corrupt-download',
defaultMessage: 'Downloaded file is corrupt',
},
invalidModpackFiles: {
id: 'app.action-bar.install.summary.invalid-modpack-files',
defaultMessage: 'Modpack files have invalid metadata',
},
noWritePermission: {
id: 'app.action-bar.install.summary.no-write-permission',
defaultMessage: 'No permission to write',
},
couldNotSaveFiles: {
id: 'app.action-bar.install.summary.could-not-save-files',
defaultMessage: "Couldn't save files",
},
invalidFilePath: {
id: 'app.action-bar.install.summary.invalid-file-path',
defaultMessage: 'File path is invalid',
},
instanceNotFound: {
id: 'app.action-bar.install.summary.instance-not-found',
defaultMessage: "Instance couldn't be found",
},
cleanupIncomplete: {
id: 'app.action-bar.install.summary.cleanup-incomplete',
defaultMessage: "Cleanup didn't finish",
},
javaSetupFailed: {
id: 'app.action-bar.install.summary.java-setup-failed',
defaultMessage: "Java setup couldn't finish",
},
minecraftSetupFailed: {
id: 'app.action-bar.install.summary.minecraft-setup-failed',
defaultMessage: 'Minecraft setup failed',
},
loaderSetupFailed: {
id: 'app.action-bar.install.summary.loader-setup-failed',
defaultMessage: 'Loader setup failed',
},
localDataError: {
id: 'app.action-bar.install.summary.local-data-error',
defaultMessage: "Couldn't update local data",
},
unexpectedError: {
id: 'app.action-bar.install.summary.unexpected-error',
defaultMessage: 'Something went wrong',
},
})
const visibleJobStatuses = new Set<InstallJobStatus>(['queued', 'running', 'failed', 'interrupted'])
const copyDetailsStallMs = 30_000
interface ProgressSnapshot {
signature: string
changedAt: number
timeout: number | null
}
function getDisplayIconUrl(icon: string | null | undefined): string | null {
if (!icon) return null
if (/^(https?:|data:|blob:|asset:|tauri:)/.test(icon)) return icon
return convertFileSrc(icon)
}
export async function useInstallJobNotifications(opts: {
router: Router
handleError: (err: unknown) => void
onChange: () => void
}) {
const { formatMessage } = useVIntl()
const jobs = ref<InstallJobSnapshot[]>([])
const iconUrls = ref<Record<string, string | null>>({})
const instanceNames = ref<Record<string, string>>({})
const copiedJobIds = ref<Set<string>>(new Set())
const jobOrder = new Map<string, number>()
let refreshRequest = 0
let metadataRequest = 0
let nextJobOrder = 0
const copiedResetTimeouts = new Map<string, number>()
const progressSnapshots = new Map<string, ProgressSnapshot>()
function getTitle(job: InstallJobSnapshot): string {
if (job.display?.title) return job.display.title
if (job.details.type === 'instance') return job.details.name
if (job.details.type === 'modpack' && job.details.title) return job.details.title
const instanceId = installJobInstanceId(job)
return (
(instanceId ? instanceNames.value[instanceId] : null) ??
formatMessage(messages.unknownInstance)
)
}
function getText(job: InstallJobSnapshot): string {
if (job.status === 'failed' || job.status === 'interrupted') {
return getFailureSummary(job)
}
if (job.phase === 'preparing_java' && job.details.type === 'java') {
return formatMessage(javaStepMessages[job.details.step], {
version: job.details.major_version,
})
}
return formatMessage(phaseMessages[job.phase])
}
function getFailureSummary(job: InstallJobSnapshot): string {
const code = job.error?.code
const phase = job.error?.phase ?? job.phase
if (code === 'app_closed' || (job.status === 'interrupted' && code === 'interrupted')) {
return formatMessage(failureSummaryMessages.appClosed)
}
if (code === 'canceled') {
return formatMessage(failureSummaryMessages.canceled)
}
if (job.rollback_error || code === 'rollback_error') {
return formatMessage(failureSummaryMessages.cleanupIncomplete)
}
if (hasPermissionError(job)) {
return formatMessage(failureSummaryMessages.noWritePermission)
}
switch (code) {
case 'network_error':
return formatMessage(
phase === 'downloading_pack_file'
? failureSummaryMessages.packDownloadFailed
: failureSummaryMessages.downloadFailed,
)
case 'api_error':
return formatMessage(failureSummaryMessages.modrinthUnreachable)
case 'pack_error':
return formatMessage(
phase === 'downloading_pack_file'
? failureSummaryMessages.packDownloadFailed
: failureSummaryMessages.invalidModpack,
)
case 'archive_error':
return formatMessage(failureSummaryMessages.badModpackFile)
case 'parse_error':
return formatMessage(failureSummaryMessages.invalidModpack)
case 'content_error':
return formatMessage(failureSummaryMessages.invalidModpackFiles)
case 'hash_error':
return formatMessage(failureSummaryMessages.corruptDownload)
case 'filesystem_error':
return formatMessage(failureSummaryMessages.couldNotSaveFiles)
case 'path_error':
return formatMessage(failureSummaryMessages.invalidFilePath)
case 'instance_error':
return formatMessage(failureSummaryMessages.instanceNotFound)
case 'java_error':
return formatMessage(failureSummaryMessages.javaSetupFailed)
case 'loader_error':
case 'processor_error':
return formatMessage(failureSummaryMessages.loaderSetupFailed)
case 'database_error':
return formatMessage(failureSummaryMessages.localDataError)
case 'launcher_error':
case 'metadata_error':
return getFailureSummaryForPhase(phase)
default:
return getFailureSummaryForPhase(phase)
}
}
function getFailureSummaryForPhase(phase: InstallPhaseId): string {
switch (phase) {
case 'downloading_pack_file':
return formatMessage(failureSummaryMessages.packDownloadFailed)
case 'resolving_pack':
case 'reading_pack_manifest':
return formatMessage(failureSummaryMessages.invalidModpack)
case 'downloading_content':
return formatMessage(failureSummaryMessages.contentDownloadFailed)
case 'extracting_overrides':
return formatMessage(failureSummaryMessages.couldNotSaveFiles)
case 'resolving_minecraft':
case 'downloading_minecraft':
return formatMessage(failureSummaryMessages.minecraftSetupFailed)
case 'resolving_loader':
case 'running_loader_processors':
return formatMessage(failureSummaryMessages.loaderSetupFailed)
case 'preparing_java':
return formatMessage(failureSummaryMessages.javaSetupFailed)
case 'preparing_instance':
return formatMessage(failureSummaryMessages.instanceNotFound)
case 'rolling_back':
return formatMessage(failureSummaryMessages.cleanupIncomplete)
default:
return formatMessage(failureSummaryMessages.unexpectedError)
}
}
function hasPermissionError(job: InstallJobSnapshot): boolean {
const message = job.error?.message.toLowerCase() ?? ''
return (
message.includes('permission denied') ||
message.includes('access is denied') ||
message.includes('operation not permitted')
)
}
function getProgressType(job: InstallJobSnapshot): PopupNotificationProgressType | undefined {
if (!getEffectiveProgress(job)) return undefined
if (
job.phase === 'preparing_java' &&
job.details.type === 'java' &&
job.details.step === 'downloading'
) {
return 'bytes'
}
if (job.phase === 'downloading_content') {
return job.progress?.secondary ? 'bytes' : 'count'
}
if (
job.phase === 'downloading_pack_file' ||
job.phase === 'extracting_overrides' ||
job.phase === 'downloading_minecraft'
) {
return 'bytes'
}
if (job.phase === 'running_loader_processors') {
return 'count'
}
return 'percentage'
}
function getEffectiveProgress(job: InstallJobSnapshot): InstallProgress | null | undefined {
if (job.phase === 'downloading_content' && job.progress?.secondary) {
return job.progress.secondary
}
return job.progress
}
function getProgress(job: InstallJobSnapshot): number {
if (job.status === 'succeeded') return 1
if (job.status === 'failed' || job.status === 'interrupted') return 0
const progress = getEffectiveProgress(job)
if (!progress || progress.total <= 0) return 0
return Math.max(0, Math.min(1, progress.current / progress.total))
}
function isTerminalJob(job: InstallJobSnapshot): boolean {
return job.status === 'failed' || job.status === 'interrupted'
}
function canShowStalledProgressDetails(job: InstallJobSnapshot): boolean {
return (
job.status === 'running' &&
job.phase !== 'preparing_instance' &&
job.phase !== 'finalizing' &&
job.phase !== 'rolling_back'
)
}
function getJobSortRank(job: InstallJobSnapshot): number {
if (isTerminalJob(job)) return 0
if (job.status === 'queued' || job.phase === 'preparing_instance') return 2
return 1
}
function progressSignature(job: InstallJobSnapshot): string {
const progress = job.progress
const secondary = progress?.secondary
return [
job.status,
job.phase,
JSON.stringify(job.details),
progress?.current ?? '',
progress?.total ?? '',
secondary?.current ?? '',
secondary?.total ?? '',
].join(':')
}
function clearCopied(jobId: string) {
if (!copiedJobIds.value.has(jobId)) {
return
}
const timeout = copiedResetTimeouts.get(jobId)
if (timeout != null) {
window.clearTimeout(timeout)
copiedResetTimeouts.delete(jobId)
}
const nextCopiedJobIds = new Set(copiedJobIds.value)
nextCopiedJobIds.delete(jobId)
copiedJobIds.value = nextCopiedJobIds
}
function clearProgressSnapshot(jobId: string) {
const snapshot = progressSnapshots.get(jobId)
if (snapshot?.timeout != null) {
window.clearTimeout(snapshot.timeout)
}
progressSnapshots.delete(jobId)
}
function scheduleStaleProgressRefresh(jobId: string) {
const snapshot = progressSnapshots.get(jobId)
if (!snapshot) {
return
}
snapshot.timeout = window.setTimeout(() => {
const snapshot = progressSnapshots.get(jobId)
if (!snapshot) {
return
}
snapshot.timeout = null
opts.onChange()
}, copyDetailsStallMs)
}
function syncProgressSnapshots(nextJobs: InstallJobSnapshot[]) {
const trackedJobIds = new Set<string>()
const now = Date.now()
for (const job of nextJobs) {
if (!canShowStalledProgressDetails(job)) {
continue
}
trackedJobIds.add(job.job_id)
const signature = progressSignature(job)
const snapshot = progressSnapshots.get(job.job_id)
if (snapshot?.signature === signature) {
continue
}
clearProgressSnapshot(job.job_id)
clearCopied(job.job_id)
progressSnapshots.set(job.job_id, {
signature,
changedAt: now,
timeout: null,
})
scheduleStaleProgressRefresh(job.job_id)
}
for (const jobId of progressSnapshots.keys()) {
if (!trackedJobIds.has(jobId)) {
clearProgressSnapshot(jobId)
}
}
}
function hasStalledProgress(job: InstallJobSnapshot): boolean {
const snapshot = progressSnapshots.get(job.job_id)
return !!snapshot && Date.now() - snapshot.changedAt >= copyDetailsStallMs
}
function shouldShowCopyDetails(job: InstallJobSnapshot): boolean {
return isTerminalJob(job) || (canShowStalledProgressDetails(job) && hasStalledProgress(job))
}
function isCopied(job: InstallJobSnapshot): boolean {
return copiedJobIds.value.has(job.job_id)
}
function setCopied(job: InstallJobSnapshot) {
copiedJobIds.value = new Set([...copiedJobIds.value, job.job_id])
const existingTimeout = copiedResetTimeouts.get(job.job_id)
if (existingTimeout != null) {
window.clearTimeout(existingTimeout)
}
copiedResetTimeouts.set(
job.job_id,
window.setTimeout(() => {
copiedResetTimeouts.delete(job.job_id)
if (!copiedJobIds.value.has(job.job_id)) {
return
}
const nextCopiedJobIds = new Set(copiedJobIds.value)
nextCopiedJobIds.delete(job.job_id)
copiedJobIds.value = nextCopiedJobIds
opts.onChange()
}, 1_000),
)
opts.onChange()
}
async function copyJobDetails(job: InstallJobSnapshot) {
const details = await install_job_support_details(job.job_id).catch((error) => {
opts.handleError(error)
return null
})
if (!details) {
return
}
try {
await navigator.clipboard.writeText(details)
setCopied(job)
} catch (error) {
opts.handleError(error)
}
}
function getButtons(job: InstallJobSnapshot): PopupNotificationButton[] {
const buttons: PopupNotificationButton[] = []
if (isTerminalJob(job)) {
buttons.push({
label: formatMessage(messages.retry),
icon: UpdatedIcon,
color: 'brand',
keepOpen: true,
action: async () => {
await install_job_retry(job.job_id).catch(opts.handleError)
await refresh()
},
})
}
if (shouldShowCopyDetails(job)) {
const copied = isCopied(job)
buttons.push({
label: formatMessage(copied ? messages.copied : messages.copyDetails),
icon: copied ? CheckIcon : CopyIcon,
color: 'standard',
keepOpen: true,
action: async () => {
await copyJobDetails(job)
},
})
}
return buttons
}
function setJobs(nextJobs: InstallJobSnapshot[]) {
for (const job of nextJobs) {
if (!jobOrder.has(job.job_id)) {
jobOrder.set(job.job_id, nextJobOrder++)
}
}
const visibleJobs = nextJobs.filter((job) => visibleJobStatuses.has(job.status))
syncProgressSnapshots(visibleJobs)
jobs.value = visibleJobs.sort(
(a, b) =>
getJobSortRank(a) - getJobSortRank(b) ||
a.created.localeCompare(b.created) ||
(jobOrder.get(a.job_id) ?? 0) - (jobOrder.get(b.job_id) ?? 0),
)
}
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
jobs.value.map((job) => {
const progress = getEffectiveProgress(job)
return {
id: job.job_id,
title: getTitle(job),
text: getText(job),
iconUrl: iconUrls.value[job.job_id] ?? null,
progress: getProgress(job),
waiting: !job.progress && ['queued', 'running'].includes(job.status),
showProgress: !isTerminalJob(job),
wrapText: isTerminalJob(job),
progressType: isTerminalJob(job) ? undefined : getProgressType(job),
progressCurrent: isTerminalJob(job) ? undefined : progress?.current,
progressTotal: isTerminalJob(job) ? undefined : progress?.total,
buttons: getButtons(job),
onDismiss: isTerminalJob(job)
? async () => {
await install_job_dismiss(job.job_id).catch(opts.handleError)
await refresh()
}
: undefined,
}
}),
)
const buttons = computed<PopupNotificationButton[] | undefined>(() => undefined)
async function refreshMetadata(notify = true) {
const request = ++metadataRequest
const sourceJobs = jobs.value
const instanceIds = Array.from(
new Set(
sourceJobs
.map((job) => installJobInstanceId(job))
.filter((instanceId): instanceId is string => !!instanceId),
),
)
const instances = instanceIds.length
? await getInstances(instanceIds).catch((error) => {
opts.handleError(error)
return []
})
: []
if (request !== metadataRequest) {
return
}
const instanceIconUrls = new Map(
instances.map((instance) => [instance.id, getDisplayIconUrl(instance.icon_path)]),
)
instanceNames.value = Object.fromEntries(
instances.map((instance) => [instance.id, instance.name]),
)
iconUrls.value = Object.fromEntries(
sourceJobs.map((job) => [
job.job_id,
getDisplayIconUrl(job.display?.icon) ??
instanceIconUrls.get(installJobInstanceId(job) ?? '') ??
null,
]),
)
if (notify) {
opts.onChange()
}
}
async function refresh(notify = true) {
const request = ++refreshRequest
const nextJobs = await install_job_list(false).catch((error) => {
opts.handleError(error)
return []
})
if (request !== refreshRequest) {
return
}
setJobs(nextJobs)
await refreshMetadata(false)
if (request !== refreshRequest) {
return
}
if (notify) {
opts.onChange()
}
}
function applyJobUpdate(job: InstallJobSnapshot) {
refreshRequest += 1
const existingJob = jobs.value.find((item) => item.job_id === job.job_id)
if (existingJob && existingJob.modified.localeCompare(job.modified) > 0) {
return
}
setJobs([...jobs.value.filter((item) => item.job_id !== job.job_id), job])
opts.onChange()
void refreshMetadata()
}
await refresh(false)
const unlisten = await install_job_listener((job: InstallJobSnapshot) => applyJobUpdate(job))
return {
active: computed(() => jobs.value.length > 0),
title: computed(() => formatMessage(messages.installs)),
progressItems,
buttons,
refresh,
dispose: () => {
for (const timeout of copiedResetTimeouts.values()) {
window.clearTimeout(timeout)
}
for (const jobId of progressSnapshots.keys()) {
clearProgressSnapshot(jobId)
}
unlisten()
},
}
}
@@ -2,19 +2,24 @@ import type { Labrinth } from '@modrinth/api-client'
import { CheckIcon, PlayIcon, PlusIcon, StopCircleIcon } from '@modrinth/assets'
import type { CardAction } from '@modrinth/ui'
import { commonMessages, defineMessages, useDebugLogger, useVIntl } from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { openUrl } from '@tauri-apps/plugin-opener'
import type { ComputedRef, Ref } from 'vue'
import { onUnmounted, ref, shallowRef } from 'vue'
import type { Router } from 'vue-router'
import {
fetchCachedServerStatus,
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import { process_listener } from '@/helpers/events'
import { get_by_profile_path } from '@/helpers/process'
import { kill, list as listInstances } from '@/helpers/profile.js'
import { kill, list as listInstances } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_profile, getServerLatency } from '@/helpers/worlds'
import { getServerAddress } from '@/store/install.js'
import { add_server_to_instance, getServerAddress } from '@/helpers/worlds'
interface BrowseServerInstance {
id: string
name: string
path: string
}
@@ -65,14 +70,13 @@ const messages = defineMessages({
export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
const { formatMessage } = useVIntl()
const queryClient = useQueryClient()
const debugLog = useDebugLogger('BrowseServer')
const serverPings = shallowRef<Record<string, number | undefined>>({})
const serverPingCache = new Map<string, number | undefined>()
const pendingServerPings = new Map<string, Promise<number | undefined>>()
const runningServerProjects = ref<Record<string, string>>({})
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
const contextMenuRef = ref<ContextMenuHandle | null>(null)
let serverPingCacheActive = true
let serverPingsActive = true
let unlistenProcesses: (() => void) | null = null
async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
@@ -83,13 +87,11 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
})
const newRunning: Record<string, string> = {}
for (const hit of hits) {
const inst = packs.find(
(pack: GameInstance) => pack.linked_data?.project_id === hit.project_id,
)
const inst = packs.find((pack: GameInstance) => pack.link?.project_id === hit.project_id)
if (inst) {
const processes = await get_by_profile_path(inst.path).catch(() => [])
const processes = await get_by_instance_id(inst.id).catch(() => [])
if (Array.isArray(processes) && processes.length > 0) {
newRunning[hit.project_id] = inst.path
newRunning[hit.project_id] = inst.id
}
}
}
@@ -99,9 +101,9 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
async function handleStopServerProject(projectId: string) {
debugLog('handleStopServerProject', projectId)
const instancePath = runningServerProjects.value[projectId]
if (!instancePath) return
await kill(instancePath).catch(() => {})
const instanceId = runningServerProjects.value[projectId]
if (!instanceId) return
await kill(instanceId).catch(() => {})
const { [projectId]: _, ...rest } = runningServerProjects.value
runningServerProjects.value = rest
}
@@ -118,9 +120,10 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
if (!address) return
if (options.instance.value) {
const instanceId = options.instance.value.id
try {
await add_server_to_profile(
options.instance.value.path,
await add_server_to_instance(
instanceId,
project.name,
address,
'prompt',
@@ -128,6 +131,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
project.minecraft_java_server?.content?.kind,
)
options.newlyInstalled.value.push(project.project_id)
await queryClient.invalidateQueries({ queryKey: ['worlds', instanceId] })
} catch (error) {
options.handleError(error)
}
@@ -145,37 +149,26 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
})
const nextPings = { ...serverPings.value }
for (const { hit, address } of pingsToFetch) {
if (serverPingCache.has(address)) {
nextPings[hit.project_id] = serverPingCache.get(address)
const cachedStatus = getFreshCachedServerStatus(queryClient, address)
if (cachedStatus) {
nextPings[hit.project_id] = cachedStatus.ping
}
}
serverPings.value = nextPings
await Promise.all(
pingsToFetch.map(async ({ hit, address }) => {
if (serverPingCache.has(address)) return
if (getFreshCachedServerStatus(queryClient, address)) return
let pending = pendingServerPings.get(address)
if (!pending) {
pending = getServerLatency(address)
.then((latency) => {
if (serverPingCacheActive) serverPingCache.set(address, latency)
return latency
})
.catch((error) => {
console.error(`Failed to ping server ${address}:`, error)
if (serverPingCacheActive) serverPingCache.set(address, undefined)
return undefined
})
.finally(() => {
pendingServerPings.delete(address)
})
pendingServerPings.set(address, pending)
try {
const status = await fetchCachedServerStatus(queryClient, address)
if (!serverPingsActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: status.ping }
} catch (error) {
console.error(`Failed to ping server ${address}:`, error)
if (!serverPingsActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: undefined }
}
const latency = await pending
if (!serverPingCacheActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: latency }
}),
)
}
@@ -289,11 +282,11 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
}
}
process_listener((event: { event: string; profile_path_id: string }) => {
process_listener((event: { event: string; instance_id: string }) => {
debugLog('process event', event)
if (event.event === 'finished') {
const projectId = Object.entries(runningServerProjects.value).find(
([, path]) => path === event.profile_path_id,
([, path]) => path === event.instance_id,
)?.[0]
if (projectId) {
const { [projectId]: _, ...rest } = runningServerProjects.value
@@ -307,10 +300,8 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
.catch(options.handleError)
onUnmounted(() => {
serverPingCacheActive = false
serverPingsActive = false
unlistenProcesses?.()
serverPingCache.clear()
pendingServerPings.clear()
})
return {
@@ -0,0 +1,50 @@
import type { QueryClient } from '@tanstack/vue-query'
import {
get_server_status,
normalizeServerAddress,
type ProtocolVersion,
type ServerStatus,
} from '@/helpers/worlds'
export const SERVER_STATUS_CACHE_MS = 10 * 60 * 1000
function getProtocolVersionKey(protocolVersion: ProtocolVersion | null) {
if (!protocolVersion) return 'default'
return `${protocolVersion.version}:${protocolVersion.legacy ? 'legacy' : 'modern'}`
}
export function getServerStatusQueryKey(
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
return [
'minecraft-server-status',
normalizeServerAddress(address) || address.trim().toLowerCase(),
getProtocolVersionKey(protocolVersion),
] as const
}
export function getFreshCachedServerStatus(
queryClient: QueryClient,
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
const queryKey = getServerStatusQueryKey(address, protocolVersion)
const updatedAt = queryClient.getQueryState(queryKey)?.dataUpdatedAt ?? 0
if (!updatedAt || Date.now() - updatedAt >= SERVER_STATUS_CACHE_MS) return undefined
return queryClient.getQueryData<ServerStatus>(queryKey)
}
export async function fetchCachedServerStatus(
queryClient: QueryClient,
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
return await queryClient.fetchQuery({
queryKey: getServerStatusQueryKey(address, protocolVersion),
queryFn: () => get_server_status(address, protocolVersion),
staleTime: SERVER_STATUS_CACHE_MS,
gcTime: SERVER_STATUS_CACHE_MS,
})
}
@@ -22,8 +22,8 @@ interface InstanceConsoleEntry {
const instances = new Map<string, InstanceConsoleEntry>()
function getOrCreate(profilePathId: string): InstanceConsoleEntry {
let entry = instances.get(profilePathId)
function getOrCreate(instanceId: string): InstanceConsoleEntry {
let entry = instances.get(instanceId)
if (entry) return entry
entry = {
@@ -32,25 +32,25 @@ function getOrCreate(profilePathId: string): InstanceConsoleEntry {
historicalCache: new Map(),
logList: null,
}
instances.set(profilePathId, entry)
instances.set(instanceId, entry)
return entry
}
async function hydrate(profilePathId: string): Promise<void> {
const entry = getOrCreate(profilePathId)
async function hydrate(instanceId: string): Promise<void> {
const entry = getOrCreate(instanceId)
if (entry.liveConsole.output.value.length > 0) return
const buffer = await get_live_log_buffer(profilePathId)
const buffer = await get_live_log_buffer(instanceId)
if (buffer) {
entry.liveConsole.addLegacyLog(buffer)
}
}
async function getHistoricalLogs(profilePathId: string, instancePath: string): Promise<LogEntry[]> {
const entry = getOrCreate(profilePathId)
async function getHistoricalLogs(instanceId: string): Promise<LogEntry[]> {
const entry = getOrCreate(instanceId)
if (entry.logList) return entry.logList
const logs: LogEntry[] = await get_logs(instancePath, true)
const logs: LogEntry[] = await get_logs(instanceId, true)
entry.logList = logs
for (const log of logs) {
@@ -62,38 +62,38 @@ async function getHistoricalLogs(profilePathId: string, instancePath: string): P
return logs
}
function getHistoricalContent(profilePathId: string, filename: string): string | undefined {
return instances.get(profilePathId)?.historicalCache.get(filename)
function getHistoricalContent(instanceId: string, filename: string): string | undefined {
return instances.get(instanceId)?.historicalCache.get(filename)
}
function invalidate(profilePathId: string): void {
const entry = instances.get(profilePathId)
function invalidate(instanceId: string): void {
const entry = instances.get(instanceId)
if (!entry) return
entry.historicalCache.clear()
entry.logList = null
}
async function clearLive(profilePathId: string): Promise<void> {
const entry = getOrCreate(profilePathId)
async function clearLive(instanceId: string): Promise<void> {
const entry = getOrCreate(instanceId)
entry.liveConsole.clear()
await clear_log_buffer(profilePathId).catch(() => {})
await clear_log_buffer(instanceId).catch(() => {})
}
async function destroy(profilePathId: string): Promise<void> {
instances.delete(profilePathId)
await clear_log_buffer(profilePathId).catch(() => {})
async function destroy(instanceId: string): Promise<void> {
instances.delete(instanceId)
await clear_log_buffer(instanceId).catch(() => {})
}
export function useInstanceConsole(profilePathId: string) {
const entry = getOrCreate(profilePathId)
export function useInstanceConsole(instanceId: string) {
const entry = getOrCreate(instanceId)
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),
hydrate: () => hydrate(instanceId),
getHistoricalLogs: () => getHistoricalLogs(instanceId),
getHistoricalContent: (filename: string) => getHistoricalContent(instanceId, filename),
invalidate: () => invalidate(instanceId),
clearLive: () => clearLive(instanceId),
destroy: () => destroy(instanceId),
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
+25 -11
View File
@@ -31,8 +31,8 @@ import { listen } from '@tauri-apps/api/event'
pack_name: name of the pack
pack_id, optional, the id of the modpack
pack_version, optional, the version of the modpack
profile_name: name of the profile
profile_uuid: unique identification of the profile
instance_name: name of the instance
instance_id: unique identification of the instance
}
loader_uuid: unique identification of the loading bar
@@ -57,18 +57,32 @@ export async function process_listener(callback) {
return await listen('process', (event) => callback(event.payload))
}
/// Payload for the 'profile' event
/// Payload for the 'instance' event
/*
ProfilePayload {
uuid: unique identification of the process in the state (currently identified by path, but that will change)
name: name of the profile
profile_path: relative path toprofile_listener profile (used for path identification)
path: path to profile (used for opening the profile in the OS file explorer)
InstancePayload {
instance_id: unique identification of the instance
event: event type ("Created", "Added", "Edited", "Removed")
}
*/
export async function profile_listener(callback) {
return await listen('profile', (event) => callback(event.payload))
export async function instance_listener(callback) {
return await listen('instance', (event) => callback(event.payload))
}
/// Payload for the 'instance_bulk_update_progress' event
/*
InstanceBulkUpdateProgress {
instanceId: string
stage: "resolving_versions" | "downloading" | "finishing"
current: number
total: number
}
*/
export async function instance_bulk_update_progress_listener(callback) {
return await listen('instance_bulk_update_progress', (event) => callback(event.payload))
}
export async function install_job_listener(callback) {
return await listen('install_job', (event) => callback(event.payload))
}
/// Payload for the 'command' event
@@ -105,7 +119,7 @@ export async function notification_listener(callback) {
/// Payload for the 'log' event
/*
LogPayload {
profile_path_id: string,
instance_id: string,
type: "log4j" | "legacy",
// log4j fields (when type === "log4j"):
timestamp_millis?: number,
+1 -1
View File
@@ -8,7 +8,7 @@ import type { ModrinthCredentials } from '@/helpers/mr_auth'
export type UserStatus = {
user_id: string
profile_name: string | null
instance_name: string | null
last_update: string
}
+3 -14
View File
@@ -1,11 +1,11 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
import { create } from './profile'
import { install_import_instance } from './install'
/*
API for importing instances from other launchers
@@ -32,19 +32,8 @@ export async function get_importable_instances(launcherType, basePath) {
}
/// Import an instance from a launcher type and base path
/// eg: import_instance("profile-name-to-go-to", "MultiMC", "C:/MultiMC", "Instance 1")
export async function import_instance(launcherType, basePath, instanceFolder) {
// create a basic, empty instance (most properties will be filled in by the import process)
// We do NOT watch the fs for changes to avoid duplicate events during installation
// fs watching will be enabled once the instance is imported
const profilePath = await create(instanceFolder, '1.19.4', 'vanilla', 'latest', null, true)
return await invoke('plugin:import|import_instance', {
profilePath,
launcherType,
basePath,
instanceFolder,
})
return await install_import_instance(launcherType, basePath, instanceFolder)
}
/// Checks if this instance is valid for importing, given a certain launcher type
+301
View File
@@ -0,0 +1,301 @@
import { invoke } from '@tauri-apps/api/core'
import { install_job_listener } from './events'
import type { InstanceLink, InstanceLoader } from './types'
export interface PackLocationVersionId {
type: 'fromVersionId'
project_id: string
version_id: string
title: string
icon_url?: string | null
}
export interface PackLocationFile {
type: 'fromFile'
path: string
}
export type CreatePackLocation = PackLocationVersionId | PackLocationFile
export interface InstallModpackPreview {
name: string
gameVersion: string
modloader: InstanceLoader
loaderVersion: string | null
icon?: string | null
iconUrl?: string | null
link?: InstanceLink | null
unknownFile: boolean
}
export interface InstallCreateInstanceRequest {
name: string
gameVersion: string
loader: InstanceLoader
loaderVersion: string | null
iconPath: string | null
link?: InstanceLink | null
}
export interface InstallPostInstallEdit {
name?: string | null
iconPath?: string | null
link?: InstanceLink | null
}
export type InstallJobStatus =
| 'queued'
| 'running'
| 'succeeded'
| 'failed'
| 'interrupted'
| 'canceled'
export type InstallPhaseId =
| 'preparing_instance'
| 'resolving_pack'
| 'downloading_pack_file'
| 'reading_pack_manifest'
| 'downloading_content'
| 'extracting_overrides'
| 'resolving_minecraft'
| 'resolving_loader'
| 'preparing_java'
| 'downloading_minecraft'
| 'running_loader_processors'
| 'finalizing'
| 'rolling_back'
export interface InstallProgress {
current: number
total: number
secondary?: InstallProgressSecondary | null
}
export interface InstallProgressSecondary {
current: number
total: number
}
export type InstallJavaStep =
| 'resolving'
| 'fetching_metadata'
| 'downloading'
| 'extracting'
| 'validating'
export interface InstallErrorView {
code: string
phase?: InstallPhaseId | null
message: string
api?: {
error: string
status?: number | null
method?: string | null
url?: string | null
route?: string | null
} | null
context?: {
operation: string
source_path?: string | null
target_path?: string | null
file_path?: string | null
entry_path?: string | null
urls?: string[]
expected_hash?: string | null
expected_size?: number | null
project_id?: string | null
version_id?: string | null
minecraft_version?: string | null
loader?: string | null
java_version?: number | null
os?: string | null
arch?: string | null
} | null
}
export interface InstallJobSnapshot {
job_id: string
instance_id?: string | null
kind:
| 'create_instance'
| 'create_modpack_instance'
| 'import_instance'
| 'duplicate_instance'
| 'install_existing_instance'
| 'install_pack_to_existing_instance'
status: InstallJobStatus
target:
| { type: 'new_instance'; instance_id?: string | null }
| { type: 'existing_instance'; instance_id: string }
phase: InstallPhaseId
progress?: InstallProgress | null
details:
| { type: 'empty' }
| { type: 'instance'; name: string }
| { type: 'minecraft'; game_version: string; loader: InstanceLoader }
| { type: 'java'; major_version: number; step: InstallJavaStep }
| {
type: 'modpack'
project_id?: string | null
version_id?: string | null
title?: string | null
}
| { type: 'import'; launcher_type: string; instance_folder: string }
display?: { title: string; icon?: string | null } | null
error?: InstallErrorView | null
rollback_error?: InstallErrorView | null
created: string
modified: string
finished?: string | null
}
export async function install_get_modpack_preview(location: CreatePackLocation) {
return await invoke<InstallModpackPreview>('plugin:install|install_get_modpack_preview', {
location,
})
}
export async function install_create_instance(request: InstallCreateInstanceRequest) {
return await invoke<InstallJobSnapshot>('plugin:install|install_create_instance', { request })
}
export async function install_create_modpack_instance(
location: CreatePackLocation,
postInstallEdit?: InstallPostInstallEdit | null,
) {
return await invoke<InstallJobSnapshot>('plugin:install|install_create_modpack_instance', {
location,
postInstallEdit,
})
}
export async function install_import_instance(
launcherType: string,
basePath: string,
instanceFolder: string,
) {
return await invoke<InstallJobSnapshot>('plugin:install|install_import_instance', {
launcherType,
basePath,
instanceFolder,
})
}
export async function install_duplicate_instance(sourceInstanceId: string) {
return await invoke<InstallJobSnapshot>('plugin:install|install_duplicate_instance', {
sourceInstanceId,
})
}
export async function install_existing_instance(instanceId: string, force: boolean) {
return await invoke<InstallJobSnapshot>('plugin:install|install_existing_instance', {
instanceId,
force,
})
}
export async function install_pack_to_existing_instance(
instanceId: string,
location: CreatePackLocation,
postInstallEdit?: InstallPostInstallEdit | null,
) {
return await invoke<InstallJobSnapshot>('plugin:install|install_pack_to_existing_instance', {
instanceId,
location,
postInstallEdit,
})
}
export async function install_job_list(includeFinished: boolean) {
return await invoke<InstallJobSnapshot[]>('plugin:install|install_job_list', { includeFinished })
}
export async function install_job_get(jobId: string) {
return await invoke<InstallJobSnapshot>('plugin:install|install_job_get', { jobId })
}
export async function install_job_retry(jobId: string) {
return await invoke<InstallJobSnapshot>('plugin:install|install_job_retry', { jobId })
}
export async function install_job_cancel(jobId: string) {
return await invoke<InstallJobSnapshot>('plugin:install|install_job_cancel', { jobId })
}
export async function install_job_dismiss(jobId: string) {
return await invoke<void>('plugin:install|install_job_dismiss', { jobId })
}
export async function install_job_support_details(jobId: string) {
return await invoke<string>('plugin:install|install_job_support_details', { jobId })
}
export function installJobInstanceId(job: InstallJobSnapshot): string | null {
return job.instance_id ?? job.target.instance_id ?? null
}
export function isInstallJobFinished(status: InstallJobStatus) {
return (
status === 'succeeded' ||
status === 'failed' ||
status === 'interrupted' ||
status === 'canceled'
)
}
function settleInstallJob(job: InstallJobSnapshot) {
if (job.status === 'succeeded') return job
throw new Error(job.error?.message ?? `Install job ${job.job_id} ${job.status}`)
}
export async function wait_for_install_job(jobId: string) {
const current = await install_job_get(jobId)
if (isInstallJobFinished(current.status)) return settleInstallJob(current)
return await new Promise<InstallJobSnapshot>((resolve, reject) => {
let finished = false
let unlisten: (() => void) | null = null
const cleanup = () => {
if (unlisten) {
unlisten()
unlisten = null
}
}
const resolveJob = (job: InstallJobSnapshot) => {
if (finished || job.job_id !== jobId || !isInstallJobFinished(job.status)) return
finished = true
cleanup()
try {
resolve(settleInstallJob(job))
} catch (err) {
reject(err)
}
}
const rejectWait = (err: unknown) => {
if (finished) return
finished = true
cleanup()
reject(err)
}
install_job_listener(resolveJob)
.then((listener) => {
if (finished) {
listener()
return
}
unlisten = listener
install_job_get(jobId).then(resolveJob).catch(rejectWait)
})
.catch(rejectWait)
})
}
@@ -10,7 +10,7 @@ import {
get_content_items,
get_linked_modpack_info,
type LinkedModpackInfo,
} from '@/helpers/profile'
} from '@/helpers/instance'
import { get_categories } from '@/helpers/tags.js'
import type { CacheBehaviour } from '@/helpers/types'
+333
View File
@@ -0,0 +1,333 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import type { Labrinth } from '@modrinth/api-client'
import type { ContentItem, ContentOwner } from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core'
import type { InstallJobSnapshot } from './install'
import type {
CacheBehaviour,
ContentFile,
ContentFileProjectType,
GameInstance,
InstanceLoader,
} from './types'
export async function remove(instanceId: string): Promise<void> {
return await invoke('plugin:instance|instance_remove', { instanceId })
}
export async function get(instanceId: string): Promise<GameInstance | null> {
return await invoke('plugin:instance|instance_get', { instanceId })
}
export async function get_many(instanceIds: string[]): Promise<GameInstance[]> {
return await invoke('plugin:instance|instance_get_many', { instanceIds })
}
export async function get_projects(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<Record<string, ContentFile>> {
return await invoke('plugin:instance|instance_get_projects', { instanceId, cacheBehaviour })
}
export async function get_installed_project_ids(instanceId: string): Promise<string[]> {
return await invoke('plugin:instance|instance_get_installed_project_ids', { instanceId })
}
export type InstanceInstallTarget = {
game_version: string
loader: string
}
export type InstanceInstallCandidate = {
id: string
name: string
icon_path?: string | null
game_version: string
loader: InstanceLoader
installed: boolean
compatible: boolean
}
export async function get_install_candidates(
projectId: string,
projectType: string,
targets: InstanceInstallTarget[],
): Promise<InstanceInstallCandidate[]> {
return await invoke('plugin:instance|instance_get_install_candidates', {
projectId,
projectType,
targets,
})
}
// Get content items with rich metadata for an instance
// Returns content items filtered to exclude modpack files (if linked),
// sorted alphabetically by project name
export async function get_content_items(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_content_items', { instanceId, cacheBehaviour })
}
// Linked modpack info returned from backend
export interface LinkedModpackInfo {
project: Labrinth.Projects.v2.Project
version: Labrinth.Versions.v2.Version
owner: ContentOwner | null
has_update: boolean
update_version_id: string | null
update_version: Labrinth.Versions.v2.Version | null
}
// Get linked modpack info for an instance
// Returns project, version, and owner information for the linked modpack,
// or null if the instance is not linked to a modpack
export async function get_linked_modpack_info(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<LinkedModpackInfo | null> {
return await invoke('plugin:instance|instance_get_linked_modpack_info', {
instanceId,
cacheBehaviour,
})
}
// Get content items that are part of the linked modpack
// Returns the modpack's dependencies as ContentItem list
// Returns empty array if the instance is not linked to a modpack
export async function get_linked_modpack_content(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_linked_modpack_content', {
instanceId,
cacheBehaviour,
})
}
// Convert a list of dependencies into ContentItems with rich metadata
export async function get_dependencies_as_content_items(
dependencies: Labrinth.Versions.v3.Dependency[],
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_dependencies_as_content_items', {
dependencies,
cacheBehaviour,
})
}
export async function get_full_path(instanceId: string): Promise<string> {
return await invoke('plugin:instance|instance_get_full_path', { instanceId })
}
export async function get_mod_full_path(instanceId: string, projectPath: string): Promise<string> {
return await invoke('plugin:instance|instance_get_mod_full_path', { instanceId, projectPath })
}
export interface JavaVersion {
parsed_version: number
version: string
architecture: string
path: string
}
export async function get_optimal_jre_key(instanceId: string): Promise<JavaVersion | null> {
return await invoke('plugin:instance|instance_get_optimal_jre_key', { instanceId })
}
export async function list(): Promise<GameInstance[]> {
return await invoke('plugin:instance|instance_list')
}
export async function check_installed(instanceId: string, projectId: string): Promise<boolean> {
return await invoke('plugin:instance|instance_check_installed', { instanceId, projectId })
}
export async function update_all(instanceId: string): Promise<Record<string, string>> {
return await invoke('plugin:instance|instance_update_all', { instanceId })
}
// Updates a specified project
export async function update_project(instanceId: string, projectPath: string): Promise<string> {
return await invoke('plugin:instance|instance_update_project', { instanceId, projectPath })
}
// Add a project to an instance from a version
// Returns a path to the new project file
export type DownloadReason = 'standalone' | 'dependency' | 'modpack' | 'update'
export interface ResolutionPreferences {
game_versions?: string[]
loaders?: string[]
}
export interface ResolveContentRequest {
project_id: string
version_id?: string | null
content_type: Labrinth.Content.v3.ContentType
selected?: ResolutionPreferences
}
export interface ResolvedContent {
project_id: string
version_id: string
dependent_on_version_id?: string | null
}
export interface ResolveContentPlan {
primary: ResolvedContent
dependencies: ResolvedContent[]
skipped: Array<{
project_id: string
version_id?: string | null
dependent_on_version_id?: string | null
reason: string
}>
}
export async function add_project_from_version(
instanceId: string,
versionId: string,
reason: DownloadReason,
dependentOnVersionId?: string,
): Promise<string> {
return await invoke('plugin:instance|instance_add_project_from_version', {
instanceId,
versionId,
reason,
dependentOnVersionId,
})
}
export async function install_project_with_dependencies(
instanceId: string,
request: ResolveContentRequest,
): Promise<ResolveContentPlan> {
return await invoke('plugin:instance|instance_install_project_with_dependencies', {
instanceId,
request,
})
}
export async function switch_project_version_with_dependencies(
instanceId: string,
projectPath: string,
versionId: string,
): Promise<string> {
return await invoke('plugin:instance|instance_switch_project_version_with_dependencies', {
instanceId,
projectPath,
versionId,
})
}
// Add a project to an instance from a path + project_type
// Returns a path to the new project file
export async function add_project_from_path(
instanceId: string,
projectPath: string,
projectType?: ContentFileProjectType,
): Promise<string> {
return await invoke('plugin:instance|instance_add_project_from_path', {
instanceId,
projectPath,
projectType,
})
}
// Toggle disabling a project
export async function toggle_disable_project(
instanceId: string,
projectPath: string,
desiredEnabled?: boolean,
): Promise<string> {
return await invoke('plugin:instance|instance_toggle_disable_project', {
instanceId,
projectPath,
desiredEnabled,
})
}
// Remove a project
export async function remove_project(instanceId: string, projectPath: string): Promise<void> {
return await invoke('plugin:instance|instance_remove_project', { instanceId, projectPath })
}
// Update a managed Modrinth instance to a specific version
export async function update_managed_modrinth_version(
instanceId: string,
versionId: string,
): Promise<InstallJobSnapshot> {
return await invoke('plugin:instance|instance_update_managed_modrinth_version', {
instanceId,
versionId,
})
}
// Repair a managed Modrinth instance
export async function update_repair_modrinth(instanceId: string): Promise<InstallJobSnapshot> {
return await invoke('plugin:instance|instance_repair_managed_modrinth', { instanceId })
}
// Export an instance to .mrpack
// included_overrides is an array of paths to override folders to include (ie: 'mods', 'resource_packs')
// Version id is optional (ie: 1.1.5)
export async function export_instance_mrpack(
instanceId: string,
exportLocation: string,
includedOverrides: string[],
versionId?: string,
description?: string,
name?: string,
): Promise<void> {
return await invoke('plugin:instance|instance_export_mrpack', {
instanceId,
exportLocation,
includedOverrides,
versionId,
description,
name,
})
}
// Given a folder path, populate an array of all the subfolders
// Intended to be used for finding potential override folders
// profile
// -- mods
// -- resourcepacks
// -- file1
// => [mods, resourcepacks]
// allows selection for 'included_overrides' in export_instance_mrpack
export async function get_pack_export_candidates(instanceId: string): Promise<string[]> {
return await invoke('plugin:instance|instance_get_pack_export_candidates', { instanceId })
}
// Run Minecraft using an instance
// Returns PID of child
export async function run(
instanceId: string,
serverAddress: string | null = null,
): Promise<unknown> {
return await invoke('plugin:instance|instance_run', { instanceId, serverAddress })
}
export async function kill(instanceId: string): Promise<void> {
return await invoke('plugin:instance|instance_kill', { instanceId })
}
// Edits an instance
export async function edit(instanceId: string, editInstance: Partial<GameInstance>): Promise<void> {
return await invoke('plugin:instance|instance_edit', { instanceId, editInstance })
}
// Edits an instance's icon
export async function edit_icon(instanceId: string, iconPath: string | null): Promise<void> {
return await invoke('plugin:instance|instance_edit_icon', { instanceId, iconPath })
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
+25 -25
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
@@ -14,41 +14,41 @@ pub struct Logs {
}
*/
/// Get all logs that exist for a given profile
/// Get all logs that exist for a given instance
/// This is returned as an array of Log objects, sorted by filename (the folder name, when the log was created)
export async function get_logs(profilePath, clearContents) {
return await invoke('plugin:logs|logs_get_logs', { profilePath, clearContents })
export async function get_logs(instanceId, clearContents) {
return await invoke('plugin:logs|logs_get_logs', { instanceId, clearContents })
}
/// Get a profile's log by filename
export async function get_logs_by_filename(profilePath, logType, filename) {
return await invoke('plugin:logs|logs_get_logs_by_filename', { profilePath, logType, filename })
/// Get an instance's log by filename
export async function get_logs_by_filename(instanceId, logType, filename) {
return await invoke('plugin:logs|logs_get_logs_by_filename', { instanceId, logType, filename })
}
/// Get a profile's log text only by filename
export async function get_output_by_filename(profilePath, logType, filename) {
/// Get an instance's log text only by filename
export async function get_output_by_filename(instanceId, logType, filename) {
return await invoke('plugin:logs|logs_get_output_by_filename', {
profilePath,
instanceId,
logType,
filename,
})
}
/// Delete a profile's log by filename
export async function delete_logs_by_filename(profilePath, logType, filename) {
/// Delete an instance's log by filename
export async function delete_logs_by_filename(instanceId, logType, filename) {
return await invoke('plugin:logs|logs_delete_logs_by_filename', {
profilePath,
instanceId,
logType,
filename,
})
}
/// Delete all logs for a given profile
export async function delete_logs(profilePath) {
return await invoke('plugin:logs|logs_delete_logs', { profilePath })
/// Delete all logs for a given instance
export async function delete_logs(instanceId) {
return await invoke('plugin:logs|logs_delete_logs', { instanceId })
}
/// Get the latest log for a given profile and cursor (startpoint to read withi nthe file)
/// Get the latest log for a given instance and cursor (startpoint to read within the file)
/// Returns:
/*
{
@@ -59,16 +59,16 @@ export async function delete_logs(profilePath) {
*/
// From latest.log directly
export async function get_latest_log_cursor(profilePath, cursor) {
return await invoke('plugin:logs|logs_get_latest_log_cursor', { profilePath, cursor })
export async function get_latest_log_cursor(instanceId, cursor) {
return await invoke('plugin:logs|logs_get_latest_log_cursor', { instanceId, 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 })
/// Get all buffered live log lines for an instance from the Rust ring buffer
export async function get_live_log_buffer(instanceId) {
return await invoke('plugin:logs|logs_get_live_log_buffer', { instanceId })
}
/// 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 })
/// Clear the live log buffer for an instance on the Rust side
export async function clear_log_buffer(instanceId) {
return await invoke('plugin:logs|logs_clear_live_log_buffer', { instanceId })
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
-105
View File
@@ -1,105 +0,0 @@
import { invoke } from '@tauri-apps/api/core'
import { create } from './profile'
import type { InstanceLoader } from './types'
interface PackProfileCreator {
name: string
gameVersion: string
modloader: InstanceLoader
loaderVersion: string | null
unknownFile: boolean
}
interface PackLocationVersionId {
type: 'fromVersionId'
project_id: string
version_id: string
title: string
icon_url?: string
}
interface PackLocationFile {
type: 'fromFile'
path: string
}
export async function create_profile_and_install(
projectId: string,
versionId: string,
packTitle: string,
iconUrl?: string,
createInstanceCallback: (profile: string) => void = () => {},
): Promise<void> {
const location: PackLocationVersionId = {
type: 'fromVersionId',
project_id: projectId,
version_id: versionId,
title: packTitle,
icon_url: iconUrl,
}
const profile_creator = await invoke<PackProfileCreator>(
'plugin:pack|pack_get_profile_from_pack',
{ location },
)
const profile = await create(
profile_creator.name,
profile_creator.gameVersion,
profile_creator.modloader,
profile_creator.loaderVersion,
null,
true,
)
createInstanceCallback(profile)
return await invoke('plugin:pack|pack_install', { location, profile })
}
export async function install_to_existing_profile(
projectId: string,
versionId: string,
title: string,
profilePath: string,
): Promise<void> {
const location: PackLocationVersionId = {
type: 'fromVersionId',
project_id: projectId,
version_id: versionId,
title,
}
return await invoke('plugin:pack|pack_install', { location, profile: profilePath })
}
export async function create_profile_and_install_from_file(
path: string,
showUnknownPackWarningModal?: (createProfile: () => Promise<void>, fileName: string) => void,
): Promise<void> {
const location: PackLocationFile = {
type: 'fromFile',
path,
}
const profile_creator = await invoke<PackProfileCreator>(
'plugin:pack|pack_get_profile_from_pack',
{ location },
)
const createProfile = async () => {
const profile = await create(
profile_creator.name,
profile_creator.gameVersion,
profile_creator.modloader,
profile_creator.loaderVersion,
null,
true,
)
await invoke('plugin:pack|pack_install', { location, profile })
}
if (profile_creator.unknownFile && showUnknownPackWarningModal) {
const splitPath = path.split(/[\\/]/)
const fileName = splitPath ? splitPath[splitPath.length - 1] : path
showUnknownPackWarningModal(createProfile, fileName)
} else {
await createProfile()
}
}
+5 -5
View File
@@ -1,17 +1,17 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
/// Gets all running process IDs with a given profile path
/// Gets all running process IDs with a given instance ID
/// Returns [u32]
export async function get_by_profile_path(path) {
return await invoke('plugin:process|process_get_by_profile_path', { path })
export async function get_by_instance_id(instanceId) {
return await invoke('plugin:process|process_get_by_instance_id', { instanceId })
}
/// Gets all running process IDs with a given profile path
/// Gets all running process IDs
/// Returns [u32]
export async function get_all() {
return await invoke('plugin:process|process_get_all')
-317
View File
@@ -1,317 +0,0 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import type { Labrinth } from '@modrinth/api-client'
import type { ContentItem, ContentOwner } from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core'
import { install_to_existing_profile } from '@/helpers/pack'
import type {
CacheBehaviour,
ContentFile,
ContentFileProjectType,
GameInstance,
InstanceLoader,
} from './types'
// Add instance
/*
name: String, // the name of the profile, and relative path to create
game_version: String, // the game version of the profile
modloader: ModLoader, // the modloader to use
- ModLoader is an enum, with the following variants: Vanilla, Forge, Fabric, Quilt
loader_version: String, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader
icon: Path, // the icon for the profile
- icon is a path to an image file, which will be copied into the profile directory
*/
export async function create(
name: string,
gameVersion: string,
modloader: InstanceLoader,
loaderVersion: string | null,
icon: string | null,
skipInstall: boolean,
linkedData?: { project_id: string; version_id: string; locked: boolean } | null,
): Promise<string> {
// Trim string name to avoid "Unable to find directory"
name = name.trim()
return await invoke('plugin:profile-create|profile_create', {
name,
gameVersion,
modloader,
loaderVersion,
icon,
skipInstall,
linkedData,
})
}
// duplicate a profile
export async function duplicate(path: string): Promise<string> {
return await invoke('plugin:profile-create|profile_duplicate', { path })
}
// Remove a profile
export async function remove(path: string): Promise<void> {
return await invoke('plugin:profile|profile_remove', { path })
}
// Get a profile by path
// Returns a Profile
export async function get(path: string): Promise<GameInstance | null> {
return await invoke('plugin:profile|profile_get', { path })
}
export async function get_many(paths: string[]): Promise<GameInstance[]> {
return await invoke('plugin:profile|profile_get_many', { paths })
}
// Get a profile's projects
// Returns a map of a path to profile file
export async function get_projects(
path: string,
cacheBehaviour?: CacheBehaviour,
): Promise<Record<string, ContentFile>> {
return await invoke('plugin:profile|profile_get_projects', { path, cacheBehaviour })
}
// Get just the installed project IDs for a profile (lightweight, skips update checks)
export async function get_installed_project_ids(path: string): Promise<string[]> {
return await invoke('plugin:profile|profile_get_installed_project_ids', { path })
}
// Get content items with rich metadata for a profile
// Returns content items filtered to exclude modpack files (if linked),
// sorted alphabetically by project name
export async function get_content_items(
path: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:profile|profile_get_content_items', { path, cacheBehaviour })
}
// Linked modpack info returned from backend
export interface LinkedModpackInfo {
project: Labrinth.Projects.v2.Project
version: Labrinth.Versions.v2.Version
owner: ContentOwner | null
has_update: boolean
update_version_id: string | null
update_version: Labrinth.Versions.v2.Version | null
}
// Get linked modpack info for a profile
// Returns project, version, and owner information for the linked modpack,
// or null if the profile is not linked to a modpack
export async function get_linked_modpack_info(
path: string,
cacheBehaviour?: CacheBehaviour,
): Promise<LinkedModpackInfo | null> {
return await invoke('plugin:profile|profile_get_linked_modpack_info', { path, cacheBehaviour })
}
// Get content items that are part of the linked modpack
// Returns the modpack's dependencies as ContentItem list
// Returns empty array if the profile is not linked to a modpack
export async function get_linked_modpack_content(
path: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:profile|profile_get_linked_modpack_content', { path, cacheBehaviour })
}
// Convert a list of dependencies into ContentItems with rich metadata
export async function get_dependencies_as_content_items(
dependencies: Labrinth.Versions.v3.Dependency[],
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:profile|profile_get_dependencies_as_content_items', {
dependencies,
cacheBehaviour,
})
}
// Get a profile's full fs path
// Returns a path
export async function get_full_path(path: string): Promise<string> {
return await invoke('plugin:profile|profile_get_full_path', { path })
}
// Get's a mod's full fs path
// Returns a path
export async function get_mod_full_path(path: string, projectPath: string): Promise<string> {
return await invoke('plugin:profile|profile_get_mod_full_path', { path, projectPath })
}
export interface JavaVersion {
parsed_version: number
version: string
architecture: string
path: string
}
// Get optimal java version from profile
// Returns a java version
export async function get_optimal_jre_key(path: string): Promise<JavaVersion | null> {
return await invoke('plugin:profile|profile_get_optimal_jre_key', { path })
}
// Get a copy of the profile set
// Returns hashmap of path -> Profile
export async function list(): Promise<GameInstance[]> {
return await invoke('plugin:profile|profile_list')
}
export async function check_installed(path: string, projectId: string): Promise<boolean> {
return await invoke('plugin:profile|profile_check_installed', { path, projectId })
}
export async function check_installed_batch(projectId: string): Promise<Record<string, boolean>> {
return await invoke('plugin:profile|profile_check_installed_batch', { projectId })
}
// Installs/Repairs a profile
export async function install(path: string, force: boolean): Promise<void> {
return await invoke('plugin:profile|profile_install', { path, force })
}
// Updates all of a profile's projects
export async function update_all(path: string): Promise<Record<string, string>> {
return await invoke('plugin:profile|profile_update_all', { path })
}
// Updates a specified project
export async function update_project(path: string, projectPath: string): Promise<string> {
return await invoke('plugin:profile|profile_update_project', { path, projectPath })
}
// Add a project to a profile from a version
// Returns a path to the new project file
export type DownloadReason = 'standalone' | 'dependency' | 'modpack' | 'update'
export async function add_project_from_version(
path: string,
versionId: string,
reason: DownloadReason,
dependentOnVersionId?: string,
): Promise<string> {
return await invoke('plugin:profile|profile_add_project_from_version', {
path,
versionId,
reason,
dependentOnVersionId,
})
}
// Add a project to a profile from a path + project_type
// Returns a path to the new project file
export async function add_project_from_path(
path: string,
projectPath: string,
projectType?: ContentFileProjectType,
): Promise<string> {
return await invoke('plugin:profile|profile_add_project_from_path', {
path,
projectPath,
projectType,
})
}
// Toggle disabling a project
export async function toggle_disable_project(path: string, projectPath: string): Promise<string> {
return await invoke('plugin:profile|profile_toggle_disable_project', { path, projectPath })
}
// Remove a project
export async function remove_project(path: string, projectPath: string): Promise<void> {
return await invoke('plugin:profile|profile_remove_project', { path, projectPath })
}
// Update a managed Modrinth profile to a specific version
export async function update_managed_modrinth_version(
path: string,
versionId: string,
): Promise<void> {
return await invoke('plugin:profile|profile_update_managed_modrinth_version', {
path,
versionId,
})
}
// Repair a managed Modrinth profile
export async function update_repair_modrinth(path: string): Promise<void> {
return await invoke('plugin:profile|profile_repair_managed_modrinth', { path })
}
// Export a profile to .mrpack
// included_overrides is an array of paths to override folders to include (ie: 'mods', 'resource_packs')
// Version id is optional (ie: 1.1.5)
export async function export_profile_mrpack(
path: string,
exportLocation: string,
includedOverrides: string[],
versionId?: string,
description?: string,
name?: string,
): Promise<void> {
return await invoke('plugin:profile|profile_export_mrpack', {
path,
exportLocation,
includedOverrides,
versionId,
description,
name,
})
}
// Given a folder path, populate an array of all the subfolders
// Intended to be used for finding potential override folders
// profile
// -- mods
// -- resourcepacks
// -- file1
// => [mods, resourcepacks]
// allows selection for 'included_overrides' in export_profile_mrpack
export async function get_pack_export_candidates(profilePath: string): Promise<string[]> {
return await invoke('plugin:profile|profile_get_pack_export_candidates', { profilePath })
}
// Run Minecraft using a pathed profile
// Returns PID of child
export async function run(path: string, serverAddress: string | null = null): Promise<unknown> {
return await invoke('plugin:profile|profile_run', { path, serverAddress })
}
export async function kill(path: string): Promise<void> {
return await invoke('plugin:profile|profile_kill', { path })
}
// Edits a profile
export async function edit(path: string, editProfile: Partial<GameInstance>): Promise<void> {
return await invoke('plugin:profile|profile_edit', { path, editProfile })
}
// Edits a profile's icon
export async function edit_icon(path: string, iconPath: string | null): Promise<void> {
return await invoke('plugin:profile|profile_edit_icon', { path, iconPath })
}
export async function finish_install(instance: GameInstance): Promise<void> {
if (instance.install_stage !== 'pack_installed') {
const linkedData = instance.linked_data
if (linkedData) {
await install_to_existing_profile(
linkedData.project_id,
linkedData.version_id,
instance.name,
instance.path,
)
}
} else {
await install(instance.path, false)
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
+3 -2
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
@@ -8,7 +8,8 @@ import { invoke } from '@tauri-apps/api/core'
export interface LoadingBarType {
type?: string
version?: string
profile_path?: string
instance_id?: string
instance_name?: string
pack_name?: string
icon?: string | null
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
+60 -7
View File
@@ -1,20 +1,23 @@
import type { ModrinthId } from '@modrinth/utils'
export type GameInstance = {
id: string
path: string
install_stage: InstallStage
launcher_feature_version: string
name: string
icon_path?: string
game_version: string
protocol_version?: number
loader: InstanceLoader
loader_version?: string
groups: string[]
linked_data?: LinkedData
preferred_update_channel: ReleaseChannel
link?: InstanceLink | null
update_channel: ReleaseChannel
created: Date
modified: Date
@@ -40,18 +43,61 @@ type InstallStage =
| 'pack_installing'
| 'not_installed'
type LinkedData = {
project_id: ModrinthId
version_id: ModrinthId
locked: boolean
type InstanceLinkIdentity = {
project_id?: ModrinthId | null
version_id?: ModrinthId | null
server_project_id?: ModrinthId | null
content_project_id?: ModrinthId | null
content_version_id?: ModrinthId | null
}
export type InstanceLink = InstanceLinkIdentity &
(
| {
type: 'modrinth_modpack'
project_id: ModrinthId
version_id: ModrinthId
}
| {
type: 'server_project'
project_id: ModrinthId
}
| {
type: 'server_project_modpack'
server_project_id: ModrinthId
content_project_id?: ModrinthId | null
content_version_id: ModrinthId
project_id?: ModrinthId
version_id?: ModrinthId
}
| {
type: 'imported_modpack'
project_id?: ModrinthId | null
version_id?: ModrinthId | null
name?: string | null
version_number?: string | null
filename?: string | null
}
| {
type: 'modrinth_hosting'
server_id: string
instance_ids: string[]
active_instance_id?: string | null
}
| {
type: 'shared_instance'
shared_instance_id: string
}
)
export type Instance = GameInstance
type ReleaseChannel = 'release' | 'beta' | 'alpha'
export type InstanceLoader = 'vanilla' | 'forge' | 'fabric' | 'quilt' | 'neoforge'
type ContentFile = {
enabled: boolean
metadata?: {
project_id: string
version_id: string
@@ -87,11 +133,18 @@ type Hooks = {
type Manifest = {
gameVersions: ManifestGameVersion[]
versionGroups?: ManifestVersionGroup[]
}
type ManifestGameVersion = {
id: string
stable: boolean
versionGroup?: string
loaders: ManifestLoaderVersion[]
}
type ManifestVersionGroup = {
id: string
loaders: ManifestLoaderVersion[]
}
+27 -6
View File
@@ -1,6 +1,7 @@
import { invoke } from '@tauri-apps/api/core'
import { save } from '@tauri-apps/plugin-dialog'
import { get_full_path, get_mod_full_path } from '@/helpers/profile'
import { get_full_path, get_mod_full_path } from '@/helpers/instance'
export async function isDev() {
return await invoke('is_dev')
@@ -47,14 +48,34 @@ export async function showLauncherLogsFolder() {
return await invoke('plugin:utils|show_launcher_logs_folder', {})
}
// Opens a profile's folder in the OS file explorer
export async function showProfileInFolder(path) {
const fullPath = await get_full_path(path)
export async function createInstanceShortcut(instanceName, instanceId, options = {}) {
const outputPath = await save({
defaultPath: `Modrinth - ${instanceName}`,
})
if (!outputPath) return null
return await invoke('plugin:shortcuts|create_instance_shortcut', {
instanceName,
instanceId,
outputPath,
server: options.server,
singleplayerWorld: options.singleplayerWorld,
})
}
export async function showAppDbBackupsFolder() {
return await invoke('plugin:utils|show_app_db_backups_folder', {})
}
// Opens an instance's folder in the OS file explorer
export async function showInstanceInFolder(instanceId) {
const fullPath = await get_full_path(instanceId)
return await openPath(fullPath)
}
export async function highlightModInProfile(profilePath, projectPath) {
const fullPath = await get_mod_full_path(profilePath, projectPath)
export async function highlightModInInstance(instanceId, projectPath) {
const fullPath = await get_mod_full_path(instanceId, projectPath)
return await highlightInFolder(fullPath)
}
+94 -57
View File
@@ -3,7 +3,7 @@ import { autoToHTML } from '@sfirew/minecraft-motd-parser'
import { invoke } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import { get_full_path } from '@/helpers/profile'
import { get_full_path } from '@/helpers/instance'
import { openPath } from '@/helpers/utils'
type BaseWorld = {
@@ -36,8 +36,8 @@ export type ServerWorld = BaseWorld & {
export type World = SingleplayerWorld | ServerWorld
export type WorldWithProfile = {
profile: string
export type WorldWithInstance = {
instance_id: string
} & World
export type SingleplayerGameMode = 'survival' | 'creative' | 'adventure' | 'spectator'
@@ -88,12 +88,12 @@ export type ProtocolVersion = {
export async function get_recent_worlds(
limit: number,
displayStatuses?: DisplayStatus[],
): Promise<WorldWithProfile[]> {
): Promise<WorldWithInstance[]> {
return await invoke('plugin:worlds|get_recent_worlds', { limit, displayStatuses })
}
export async function get_profile_worlds(path: string): Promise<World[]> {
return await invoke('plugin:worlds|get_profile_worlds', { path })
export async function get_instance_worlds(instanceId: string): Promise<World[]> {
return await invoke('plugin:worlds|get_instance_worlds', { instanceId })
}
export async function get_singleplayer_world(
@@ -137,16 +137,16 @@ export async function delete_world(instance: string, world: string): Promise<voi
return await invoke('plugin:worlds|delete_world', { instance, world })
}
export async function add_server_to_profile(
path: string,
export async function add_server_to_instance(
instanceId: string,
name: string,
address: string,
packStatus: ServerPackStatus,
projectId?: string,
contentKind?: string,
): Promise<number> {
return await invoke('plugin:worlds|add_server_to_profile', {
path,
return await invoke('plugin:worlds|add_server_to_instance', {
instanceId,
name,
address,
packStatus,
@@ -155,15 +155,15 @@ export async function add_server_to_profile(
})
}
export async function edit_server_in_profile(
path: string,
export async function edit_server_in_instance(
instanceId: string,
index: number,
name: string,
address: string,
packStatus: ServerPackStatus,
): Promise<void> {
return await invoke('plugin:worlds|edit_server_in_profile', {
path,
return await invoke('plugin:worlds|edit_server_in_instance', {
instanceId,
index,
name,
address,
@@ -171,12 +171,17 @@ export async function edit_server_in_profile(
})
}
export async function remove_server_from_profile(path: string, index: number): Promise<void> {
return await invoke('plugin:worlds|remove_server_from_profile', { path, index })
export async function remove_server_from_instance(
instanceId: string,
index: number,
): Promise<void> {
return await invoke('plugin:worlds|remove_server_from_instance', { instanceId, index })
}
export async function get_profile_protocol_version(path: string): Promise<ProtocolVersion | null> {
return await invoke('plugin:worlds|get_profile_protocol_version', { path })
export async function get_instance_protocol_version(
instanceId: string,
): Promise<ProtocolVersion | null> {
return await invoke('plugin:worlds|get_instance_protocol_version', { instanceId })
}
export async function get_server_status(
@@ -186,16 +191,19 @@ export async function get_server_status(
return await invoke('plugin:worlds|get_server_status', { address, protocolVersion })
}
export async function start_join_singleplayer_world(path: string, world: string): Promise<unknown> {
return await invoke('plugin:worlds|start_join_singleplayer_world', { path, world })
export async function start_join_singleplayer_world(
instanceId: string,
world: string,
): Promise<unknown> {
return await invoke('plugin:worlds|start_join_singleplayer_world', { instanceId, world })
}
export async function start_join_server(path: string, address: string): Promise<unknown> {
return await invoke('plugin:worlds|start_join_server', { path, address })
export async function start_join_server(instanceId: string, address: string): Promise<unknown> {
return await invoke('plugin:worlds|start_join_server', { instanceId, address })
}
export async function showWorldInFolder(instancePath: string, worldPath: string) {
const fullPath = await get_full_path(instancePath)
export async function showWorldInFolder(instanceId: string, worldPath: string) {
const fullPath = await get_full_path(instanceId)
return await openPath(fullPath + '/saves/' + worldPath)
}
@@ -348,6 +356,28 @@ export function resolveManagedServerWorld(
)
}
export function getServerAddress(javaServer?: { address?: string | null } | null) {
if (!javaServer) return null
return javaServer.address ?? null
}
export async function ensureManagedServerWorldExists(
instanceId: string,
serverName: string,
serverAddress: string | null,
) {
if (!instanceId || !serverAddress) return
try {
const worlds = await get_instance_worlds(instanceId)
const managedWorld = resolveManagedServerWorld(worlds, serverName, serverAddress)
if (!managedWorld) {
await add_server_to_instance(instanceId, serverName, serverAddress, 'prompt')
}
} catch (err) {
console.error('Failed to ensure managed server world exists:', err)
}
}
export async function getServerLatency(
address: string,
protocolVersion: ProtocolVersion | null = null,
@@ -374,28 +404,35 @@ export async function refreshServerData(
): Promise<void> {
const refreshTime = Date.now()
serverData.refreshing = true
await get_server_status(address, protocolVersion)
.then((status) => {
if (serverData.lastSuccessfulRefresh && serverData.lastSuccessfulRefresh > refreshTime) {
// Don't update if there was a more recent successful refresh
return
}
serverData.lastSuccessfulRefresh = Date.now()
serverData.status = status
if (status.description) {
serverData.rawMotd = status.description
serverData.renderedMotd = autoToHTML(status.description)
}
})
.finally(() => {
serverData.refreshing = false
})
.catch((err) => {
console.error(`Refreshing addr ${address}`, protocolVersion, err)
if (!protocolVersion?.legacy) {
refreshServerData(serverData, { version: 74, legacy: true }, address)
}
})
try {
const status = await get_server_status(address, protocolVersion)
if (serverData.lastSuccessfulRefresh && serverData.lastSuccessfulRefresh > refreshTime) {
// Don't update if there was a more recent successful refresh
return
}
serverData.lastSuccessfulRefresh = Date.now()
serverData.status = status
if (status.description) {
serverData.rawMotd = status.description
serverData.renderedMotd = autoToHTML(status.description)
} else {
delete serverData.rawMotd
delete serverData.renderedMotd
}
} catch (err) {
console.error(`Refreshing addr ${address}`, protocolVersion, err)
if (!protocolVersion?.legacy) {
await refreshServerData(serverData, { version: 74, legacy: true }, address)
return
}
if (!serverData.lastSuccessfulRefresh || serverData.lastSuccessfulRefresh <= refreshTime) {
delete serverData.status
delete serverData.rawMotd
delete serverData.renderedMotd
}
} finally {
serverData.refreshing = false
}
}
export function refreshServers(
@@ -414,15 +451,15 @@ export function refreshServers(
}
})
// noinspection ES6MissingAwait - handled with .then by refreshServerData already
// noinspection ES6MissingAwait - handled by refreshServerData
Object.keys(serverData).forEach((address) =>
refreshServerData(serverData[address], protocolVersion, address),
)
}
export async function refreshWorld(worlds: World[], instancePath: string, worldPath: string) {
export async function refreshWorld(worlds: World[], instanceId: string, worldPath: string) {
const index = worlds.findIndex((w) => w.type === 'singleplayer' && w.path === worldPath)
const newWorld = await get_singleplayer_world(instancePath, worldPath)
const newWorld = await get_singleplayer_world(instanceId, worldPath)
if (index !== -1) {
worlds[index] = newWorld
} else {
@@ -432,13 +469,13 @@ export async function refreshWorld(worlds: World[], instancePath: string, worldP
sortWorlds(worlds)
}
export async function handleDefaultProfileUpdateEvent(
export async function handleDefaultInstanceUpdateEvent(
worlds: World[],
instancePath: string,
e: ProfileEvent,
instanceId: string,
e: InstanceEvent,
) {
if (e.event === 'world_updated') {
await refreshWorld(worlds, instancePath, e.world)
await refreshWorld(worlds, instanceId, e.world)
}
if (e.event === 'server_joined') {
@@ -456,9 +493,9 @@ export async function handleDefaultProfileUpdateEvent(
}
}
export async function refreshWorlds(instancePath: string): Promise<World[]> {
const worlds = await get_profile_worlds(instancePath).catch((err) => {
console.error(`Error refreshing worlds for instance: ${instancePath}`, err)
export async function refreshWorlds(instanceId: string): Promise<World[]> {
const worlds = await get_instance_worlds(instanceId).catch((err) => {
console.error(`Error refreshing worlds for instance: ${instanceId}`, err)
})
if (worlds) {
sortWorlds(worlds)
@@ -489,7 +526,7 @@ export function hasWorldQuickPlaySupport(gameVersions: GameVersion[], currentVer
return versionIndex !== -1 && targetIndex !== -1 && versionIndex <= targetIndex
}
export type ProfileEvent = { profile_path_id: string } & (
export type InstanceEvent = { instance_id: string } & (
| {
event: 'servers_updated'
}
+126 -27
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "جاز تنزيل إصدار جافا {version}"
},
"app.action-bar.downloading-update": {
"message": "تحميل التحديث"
},
"app.action-bar.downloads": {
"message": "التنزيلات"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "النسخة ألرئيسية"
},
"app.action-bar.reload-to-update": {
"message": "أعد التشغيل للتحديث"
},
"app.action-bar.show-more-running-instances": {
"message": "عرض المزيد من النسخ قيد التشغيل"
},
"app.action-bar.stop-instance": {
"message": "إيقاف النسخة"
},
"app.action-bar.update": {
"message": "تحديث"
},
"app.action-bar.view-active-downloads": {
"message": "عرض التحميلات الفعالة"
},
@@ -35,12 +44,36 @@
"app.action-bar.view-logs": {
"message": "عرض التسجيل"
},
"app.appearance-settings.advanced-rendering.description": {
"message": "يتيح العرض المتقدم لتأثيرات مثل التشويش الذي قد تسبب مشكلات في الأداء دون استخدام العرض المسرع بالأجهزة."
},
"app.appearance-settings.advanced-rendering.title": {
"message": "العرض المتقدم"
},
"app.appearance-settings.color-theme.description": {
"message": "اختر لون سمتك المفضل من أجل تطبيق Modrinth App."
},
"app.appearance-settings.color-theme.title": {
"message": "لون السمة"
},
"app.appearance-settings.default-landing-page.description": {
"message": "غير الصفحة التي يفتح عليها المشغل."
},
"app.appearance-settings.default-landing-page.home": {
"message": "صفحة الرئيسية"
},
"app.appearance-settings.default-landing-page.library": {
"message": "مكتبة"
},
"app.appearance-settings.default-landing-page.title": {
"message": "الصفحة الرئيسة الافتراضية"
},
"app.appearance-settings.hide-nametag.description": {
"message": "ألغ تفعيل عرض الاسم فوق لاعبك في صفحة المظاهر."
},
"app.appearance-settings.hide-nametag.title": {
"message": "إخفاء عرض الاسم"
},
"app.auth-servers.unreachable.body": {
"message": "قد تكون خوادم مصادقة ماينكرافت معطلة حاليًا. تحقق من اتصالك بالإنترنت وحاول مرة أخرى لاحقًا."
},
@@ -59,12 +92,18 @@
"app.browse.already-added": {
"message": "مضاف فعلا"
},
"app.browse.back-to-instance": {
"message": "العودة للنموذج"
},
"app.browse.discover-content": {
"message": "استكشف محتوى"
},
"app.browse.discover-servers": {
"message": "استكشف خوادم"
},
"app.browse.hide-added-servers": {
"message": "إخفاء الخوادم المضافة مسبقًا"
},
"app.browse.server.installing": {
"message": "جاري التثبيت"
},
@@ -102,13 +141,13 @@
"message": "حذف النموذج"
},
"app.instance.modpack-already-installed.body": {
"message": "حُزْمَة التعديل هذه مثبته فعلًا في نموذج <bold>{instanceName}</bold>. هل انت متأكد بإرادة نسخه؟"
"message": "حُزْمَة التعديل مثبته مسبقًا فعلًا في نموذج <bold>{instanceName}</bold>. هل أنت متأكد بإرادة نسخه؟"
},
"app.instance.modpack-already-installed.create": {
"message": "إنشاء"
},
"app.instance.modpack-already-installed.header": {
"message": "حُزْمَة التعديل مثبتة بالفعل"
"message": "حُزْمَة التعديل مثبتة فعلًا"
},
"app.instance.modpack-already-installed.instance": {
"message": "النموذج"
@@ -126,7 +165,7 @@
"message": "تحقق من المشاريع التي أستخدمها في حزمة التعديل الخاص بي!"
},
"app.instance.mods.share-title": {
"message": "مشاركة محتوى حزمة التعديل"
"message": "مشاركة محتوى حُزْمَة التعديل"
},
"app.instance.mods.successfully-uploaded": {
"message": "تم الرفع بنجاح"
@@ -137,12 +176,6 @@
"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": "معدّل"
},
@@ -161,21 +194,9 @@
"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": "المحتوى مطلوب"
},
@@ -189,7 +210,7 @@
"message": "{count, plural, one {# تعديل} other {# تعديلات}}"
},
"app.modal.install-to-play.required-modpack": {
"message": زمة التعديل مطلوبة"
"message": ُزْمَة التعديل مطلوبة"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "يتطلب هذا الخادم تعديلات للعب. انقر فوق \"تثبيت\" لإعداد الملفات المطلوبة من Modrinth، ثم قم بتشغيله مباشرة إلى الخادم."
@@ -210,7 +231,13 @@
"message": "يلزم التحديث"
},
"app.modal.update-to-play.update-required-description": {
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى آخر اصدار لتشغيل اللعبة."
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى أحدث اصدار لتشغيل اللعبة."
},
"app.project.install-context.back-to-browse": {
"message": "العودة إلى الاستكشاف"
},
"app.project.install-context.install-content-to-instance": {
"message": "تثبيت المحتويات الى النموذج"
},
"app.settings.developer-mode-enabled": {
"message": "تم تفعيل وضع المطوّر."
@@ -236,6 +263,75 @@
"app.settings.tabs.resource-management": {
"message": "إدارة الموارد"
},
"app.skins.add-button": {
"message": "إضافة مظهر"
},
"app.skins.add-button.drag-and-drop": {
"message": "أسحب ثم أفلت"
},
"app.skins.apply-button": {
"message": "تطبيق"
},
"app.skins.delete-button": {
"message": "حذف المظهر"
},
"app.skins.delete-modal.description": {
"message": "سيتم حذف المظهر بشكل نهائي، ولايمكن إرجاعه."
},
"app.skins.delete-modal.title": {
"message": "هل أنت متأكد من حذف المظهر هذا؟"
},
"app.skins.edit-button": {
"message": "تعديل المظهر"
},
"app.skins.modal.add-skin-button": {
"message": "إضافة المظهر"
},
"app.skins.modal.add-title": {
"message": "إضافة المظهر"
},
"app.skins.modal.cape-fallback-name": {
"message": "رداء"
},
"app.skins.modal.cape-section": {
"message": "رداء"
},
"app.skins.modal.edit-title": {
"message": "تعديل المظهر"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "عدل المظهر أولا للحفظ !"
},
"app.skins.modal.no-cape-tooltip": {
"message": "بدون رداء"
},
"app.skins.modal.none-cape-option": {
"message": "لا شيء"
},
"app.skins.modal.save-skin-button": {
"message": "حفظ المظهر"
},
"app.skins.modal.saving-tooltip": {
"message": "حفظ..."
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "أرفع المظهر أولا !"
},
"app.skins.preview.edit-button": {
"message": "تعديل المظهر"
},
"app.skins.previewing-badge": {
"message": "أستعراض"
},
"app.skins.rate-limit.title": {
"message": "تمهل !"
},
"app.skins.sign-in.title": {
"message": "الرجاء تسجيل الدخول"
},
"app.skins.title": {
"message": "اختيار المظهر"
},
"app.update-popup.body.download-complete": {
"message": "انتهى تنزيل تطبيق Modrinth v{version}. أعد التحميل للتحديث الآن، أو تلقائيًا عند إغلاق تطبيق Modrinth."
},
@@ -254,9 +350,6 @@
"app.update-popup.download-complete": {
"message": "اكتمل التنزيل"
},
"app.update-popup.reload": {
"message": "إعادة تحميل"
},
"app.update-popup.title": {
"message": "تحديث متاح"
},
@@ -267,7 +360,7 @@
"message": "تم تثبيت الإصدار {version} بنجاح!"
},
"app.world.server-modal.placeholder-address": {
"message": "مثال.مودرنث.جج"
"message": "مثال (example.modrinth.gg)"
},
"app.world.server-modal.select-an-option": {
"message": "حدد خيارا"
@@ -518,6 +611,9 @@
"instance.settings.tabs.java.java-memory": {
"message": "الذاكرة المخصَّصة"
},
"instance.settings.tabs.java.java-path-placeholder": {
"message": "/path/to/java"
},
"instance.settings.tabs.window": {
"message": "النافذة"
},
@@ -593,6 +689,9 @@
"minecraft-account.add-account": {
"message": "إضافة حساب"
},
"minecraft-account.label": {
"message": "حساب ماينكرافت"
},
"minecraft-account.select-account": {
"message": "اختيار الحساب"
},
File diff suppressed because it is too large Load Diff
+11 -23
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Downloader Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Downloader opdatering"
},
"app.action-bar.downloads": {
"message": "Downloads"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Primære instance"
},
"app.action-bar.reload-to-update": {
"message": "Genindlæs for at opdater"
},
"app.action-bar.show-more-running-instances": {
"message": "Vis flere kørende instances"
},
"app.action-bar.stop-instance": {
"message": "Stop instance"
},
"app.action-bar.update": {
"message": "Opdater"
},
"app.action-bar.view-active-downloads": {
"message": "Vis aktive downloads"
},
@@ -143,11 +152,8 @@
"app.browse.server.installing": {
"message": "Installere"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Installere modpack..."
"app.content-install.no-compatible-versions": {
"message": "Ingen tilgængelige versioner passer til {compatibilityLabel}. Vælg en version for at installere. Afhængigheder bliver ikke installeret automatisk."
},
"app.export-modal.description-placeholder": {
"message": "Indtast modpack beskrivelse..."
@@ -158,18 +164,12 @@
"app.export-modal.header": {
"message": "Eksporter modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Inkludere \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpack Navn"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack navn"
},
"app.export-modal.select-files-label": {
"message": "Konfigurer hvilke filer inkludere i denne eksport"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
@@ -224,9 +224,6 @@
"app.instance.worlds.browse-servers": {
"message": "Søg servere"
},
"app.instance.worlds.delete-world-title": {
"message": "Er du sikker på din vil slette denne verden permanent?"
},
"app.instance.worlds.filter-modded": {
"message": "Moddet"
},
@@ -245,15 +242,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Ingen servere eller verdener tilføjet"
},
"app.instance.worlds.remove-server-title": {
"message": "Er du sikker på din vil fjerne {name}?"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Søg {count} verdener..."
},
"app.instance.worlds.this-server": {
"message": "denne server"
},
"app.modal.install-to-play.content-required": {
"message": "Indhold krævet"
},
@@ -473,9 +464,6 @@
"app.update-popup.download-complete": {
"message": "Download færdiggjort"
},
"app.update-popup.reload": {
"message": "Geninlæs"
},
"app.update-popup.title": {
"message": "Opdatering tilgængelig"
},
+149 -20
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Java {version} wird heruntergeladen"
},
"app.action-bar.downloading-update": {
"message": "Update wird heruntergeladen"
},
"app.action-bar.downloads": {
"message": "Downloads"
},
"app.action-bar.hide-more-running-instances": {
"message": "Weitere laufende Instanzen ausblenden"
},
"app.action-bar.install.dismiss": {
"message": "Ignorieren"
},
"app.action-bar.install.open-instance": {
"message": "Instanz öffnen"
},
"app.action-bar.install.retry": {
"message": "Wiederholen"
},
"app.action-bar.install.unknown-instance": {
"message": "Unbekannte Instanz"
},
"app.action-bar.installs": {
"message": "Installationen"
},
"app.action-bar.make-primary-instance": {
"message": "Zur primären Instanz machen"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Primäre Instanz"
},
"app.action-bar.reload-to-update": {
"message": "Zum Aktualisieren neu laden"
},
"app.action-bar.show-more-running-instances": {
"message": "Weitere laufende Instanzen anzeigen"
},
"app.action-bar.stop-instance": {
"message": "Instanz stoppen"
},
"app.action-bar.update": {
"message": "Aktualisieren"
},
"app.action-bar.view-active-downloads": {
"message": "Aktive Downloads anzeigen"
},
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Spielzeit anzeigen"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Überspringt automatisch Bestätigungen mit geringem Risiko, wie z. B. die doppelte Installation von Modpacks, das Löschen normaler Inhalte, Massenaktualisierungen, das Trennen von Modpacks und Reparaturen. Warnungen bei gefährlichen Aktionen werden weiterhin angezeigt."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Nicht essenzielle Warnungen überspringen"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Ermöglicht das Umschalten der Seitenleiste."
},
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Keine verfügbaren Versionen stimmen mit {compatibilityLabel} überein. Wähle eine Version aus, um sie trotzdem zu installieren. Abhängigkeiten werden nicht automatisch installiert."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Modpack wird installiert..."
},
"app.export-modal.description-placeholder": {
"message": "Modpaketbeschreibung eingeben..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Modpack exportieren"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\" einschliessen?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpaketname"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpaketname"
},
"app.export-modal.select-files-label": {
"message": "Konfiguriere, welche Dateien in diesem Export miteinbezogen werden"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Lade Inhalte herunter"
},
"app.install.phase.downloading_minecraft": {
"message": "Lade Minecraft herunter"
},
"app.install.phase.downloading_pack_file": {
"message": "Lade Packdatei herunter"
},
"app.install.phase.extracting_overrides": {
"message": "Überschreibungen extrahieren"
},
"app.install.phase.finalizing": {
"message": "Abschliessen"
},
"app.install.phase.preparing_instance": {
"message": "Instanz präparieren"
},
"app.install.phase.preparing_java": {
"message": "Java präparieren"
},
"app.install.phase.preparing_java.downloading": {
"message": "Java {version} herunterladen"
},
"app.install.phase.preparing_java.extracting": {
"message": "Java {version} extrahieren"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Java {version} wird heruntergeladen"
},
"app.install.phase.preparing_java.resolving": {
"message": "Java {version} präparieren"
},
"app.install.phase.preparing_java.validating": {
"message": "Java {version} validieren"
},
"app.install.phase.reading_pack_manifest": {
"message": "Paketmanifest wird gelesen"
},
"app.install.phase.resolving_loader": {
"message": "Loader wird ermittelt"
},
"app.install.phase.resolving_minecraft": {
"message": "Minecraft wird ermittelt"
},
"app.install.phase.resolving_pack": {
"message": "Inhalte werden ermittelt"
},
"app.install.phase.rolling_back": {
"message": "Wird rückgängig gemacht"
},
"app.install.phase.running_loader_processors": {
"message": "Loader-Prozesse werden ausgeführt"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten deiner Instanz werden permanent gelöscht, inlusive deiner Welten, Konfigurationen und allen installierten Inhalten."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instanz"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Lade {current, number}/{total, number} Projekte..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Update abschliessen..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Versionen werden ermittelt..."
},
"app.instance.mods.content-type-project": {
"message": "Projekt"
},
@@ -233,8 +314,17 @@
"app.instance.worlds.browse-servers": {
"message": "Server durchsuchen"
},
"app.instance.worlds.delete-world-title": {
"message": "Möchtest du diese Welt wirklich endgültig löschen?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Diese Welt wird permanent gelöscht von dieser Instanz, dies kann nicht rückgängig gemacht werden."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Lösche {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modifiziert"
@@ -254,15 +344,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.remove-server-title": {
"message": "Möchtest du {name} wirklich entfernen?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Dieser Server wird aus deiner Serverliste und aus der Serverliste im Spiel entfernt. Du kannst ihn später wieder hinzufügen, wenn du die Adresse kennst."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Entferne {name}"
},
"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"
},
@@ -419,6 +515,12 @@
"app.skins.rate-limit.title": {
"message": "Nicht so schnell!"
},
"app.skins.reorder-error.text": {
"message": "Deine Skin-Reihenfolge konnte nicht gespeichert werden."
},
"app.skins.reorder-error.title": {
"message": "Skins konnten nicht neu angeordnet werden"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -492,7 +594,7 @@
"message": "Download abgeschlossen"
},
"app.update-popup.reload": {
"message": "Neu Laden"
"message": "Zum Aktualisieren neu laden"
},
"app.update-popup.title": {
"message": "Aktualisierung verfügbar"
@@ -692,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "Name"
},
"instance.settings.tabs.general.update-channel": {
"message": "Update-Kanal"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Release-, Beta- und Alpha-Versionen werden als verfügbare Updates angezeigt."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Release- und Beta-Versionen werden als verfügbare Updates angezeigt."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Release"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Nur Release-Versionen werden als verfügbare Updates angezeigt."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Update-Kanal auswählen"
},
"instance.settings.tabs.hooks": {
"message": "Start Hooks"
},
@@ -815,6 +941,9 @@
"instance.worlds.copy_address": {
"message": "Adresse kopieren"
},
"instance.worlds.create_shortcut": {
"message": "Shortcut erstellen"
},
"instance.worlds.dont_show_on_home": {
"message": "Nicht auf Startseite anzeigen"
},
+152 -23
View File
@@ -2,17 +2,35 @@
"app.action-bar.downloading-java": {
"message": "Java {version} wird heruntergeladen"
},
"app.action-bar.downloading-update": {
"message": "Update wird heruntergeladen"
},
"app.action-bar.downloads": {
"message": "Downloads"
},
"app.action-bar.hide-more-running-instances": {
"message": "Weitere laufende Instanzen ausblenden"
},
"app.action-bar.install.dismiss": {
"message": "Ausblenden"
},
"app.action-bar.install.open-instance": {
"message": "Instanz öffnen"
},
"app.action-bar.install.retry": {
"message": "Erneut versuchen"
},
"app.action-bar.install.unknown-instance": {
"message": "Unbekannte Instanz"
},
"app.action-bar.installs": {
"message": "Installationen"
},
"app.action-bar.make-primary-instance": {
"message": "Zur primären Instanz machen"
},
"app.action-bar.no-instances-running": {
"message": "Keine aktiven Instanzen"
"message": "Keine laufenden Instanzen"
},
"app.action-bar.offline": {
"message": "Offline"
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Primäre Instanz"
},
"app.action-bar.reload-to-update": {
"message": "Zum Aktualisieren neu laden"
},
"app.action-bar.show-more-running-instances": {
"message": "Weitere laufende Instanzen anzeigen"
},
"app.action-bar.stop-instance": {
"message": "Instanz stoppen"
},
"app.action-bar.update": {
"message": "Aktualisieren"
},
"app.action-bar.view-active-downloads": {
"message": "Aktive Downloads anzeigen"
},
@@ -33,7 +57,7 @@
"message": "Instanz anzeigen"
},
"app.action-bar.view-logs": {
"message": "Protokolle anzeigen"
"message": "Logs anzeigen"
},
"app.appearance-settings.advanced-rendering.description": {
"message": "Aktiviert erweiterte Rendering-Funktionen wie Unschärfe-Effekte, welche ohne Hardware-beschleunigtes Rendering zu Leistungsproblemen führen können."
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Spielzeit anzeigen"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Überspringt automatisch Bestätigungen mit geringem Risiko, wie z. B. die doppelte Installation von Modpacks, das Löschen normaler Inhalte, Massenaktualisierungen, das Trennen von Modpacks und Reparaturen. Warnungen bei gefährlichen Aktionen werden weiterhin angezeigt."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Unwichtige Warnungen überspringen"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Ermöglicht das Umschalten der Seitenleiste."
},
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Keine verfügbaren Versionen stimmen mit {compatibilityLabel} überein. Wähle eine Version aus, um sie trotzdem zu installieren. Abhängigkeiten werden nicht automatisch installiert."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Modpack wird installiert..."
},
"app.export-modal.description-placeholder": {
"message": "Beschreibung des Modpacks eingeben..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Modpack exportieren"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\" einschließen?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpackname"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpackname"
},
"app.export-modal.select-files-label": {
"message": "Konfiguriere, welche Dateien in diesen Export enthalten sind"
},
"app.export-modal.version-number-label": {
"message": "Versionsnummer"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Inhalte werden heruntergeladen"
},
"app.install.phase.downloading_minecraft": {
"message": "Minecraft wird heruntergeladen"
},
"app.install.phase.downloading_pack_file": {
"message": "Paketdatei wird herunterladen"
},
"app.install.phase.extracting_overrides": {
"message": "Überschreibungen werden extrahiert"
},
"app.install.phase.finalizing": {
"message": "Wird finalisiert"
},
"app.install.phase.preparing_instance": {
"message": "Instanz wird vorbereitet"
},
"app.install.phase.preparing_java": {
"message": "Java wird vorbereitet"
},
"app.install.phase.preparing_java.downloading": {
"message": "Java {version} wird heruntergeladen"
},
"app.install.phase.preparing_java.extracting": {
"message": "Java {version} wird extrahiert"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Java {version} wird heruntergeladen"
},
"app.install.phase.preparing_java.resolving": {
"message": "Java {version} wird vorbereitet"
},
"app.install.phase.preparing_java.validating": {
"message": "Java {version} wird validiert"
},
"app.install.phase.reading_pack_manifest": {
"message": "Paketmanifest wird gelesen"
},
"app.install.phase.resolving_loader": {
"message": "Loader wird ermittelt"
},
"app.install.phase.resolving_minecraft": {
"message": "Minecraft wird ermittelt"
},
"app.install.phase.resolving_pack": {
"message": "Inhalte werden ermittelt"
},
"app.install.phase.rolling_back": {
"message": "Wird rückgängig gemacht"
},
"app.install.phase.running_loader_processors": {
"message": "Loader-Prozesse werden ausgeführt"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten deiner Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instanz"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} Projekte werden heruntergelanden..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Update wird fertiggestellt..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Versionen werden ermittelt..."
},
"app.instance.mods.content-type-project": {
"message": "Projekt"
},
@@ -233,8 +314,17 @@
"app.instance.worlds.browse-servers": {
"message": "Server durchsuchen"
},
"app.instance.worlds.delete-world-title": {
"message": "Möchtest du diese Welt wirklich endgültig löschen?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Welt löschen"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Die Welt wird permanent von der Instanz gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "{name} wird gelöscht"
},
"app.instance.worlds.filter-modded": {
"message": "Modifiziert"
@@ -254,15 +344,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.remove-server-title": {
"message": "Möchtest du {name} wirklich entfernen?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Server entfernen"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Dieser Server wird aus deiner Serverliste und aus der Serverliste im Spiel entfernt. Du kannst ihn später wieder hinzufügen, wenn du die Adresse kennst."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "{name} wird entfernt"
},
"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"
},
@@ -414,11 +510,17 @@
"message": "Vorschau"
},
"app.skins.rate-limit.text": {
"message": "Du wechselst dein Skin zu oft. Die Server von Mojang haben weitere Anfragen vorübergehend blockiert. Bitte warte einen Moment, bevor du es erneut versuchst."
"message": "Du wechselst deinen Skin zu oft. Die Server von Mojang haben weitere Anfragen vorübergehend blockiert. Bitte warte einen Moment, bevor du es erneut versuchst."
},
"app.skins.rate-limit.title": {
"message": "Langsamer!"
},
"app.skins.reorder-error.text": {
"message": "Die Anordnung deiner Skins konnte nicht gespeichert werden."
},
"app.skins.reorder-error.title": {
"message": "Skins konnten nicht neu angeordnet werden"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -492,7 +594,7 @@
"message": "Download abgeschlossen"
},
"app.update-popup.reload": {
"message": "Neu laden"
"message": "Zum Aktualisieren neu laden"
},
"app.update-popup.title": {
"message": "Aktualisierung verfügbar"
@@ -692,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "Name"
},
"instance.settings.tabs.general.update-channel": {
"message": "Aktualisierungskanal"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Voll, Beta- und Alpha-Versionen werden als verfügbare Aktualisierungen angezeigt."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Voll- und Beta-Versionen werden als verfügbare Aktualisierungen angezeigt."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Vollversion"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Nur Vollversionen werden als verfügbare Aktualisierungen angezeigt."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Aktualisierungskanal auswählen"
},
"instance.settings.tabs.hooks": {
"message": "Startargumente"
},
@@ -815,6 +941,9 @@
"instance.worlds.copy_address": {
"message": "Adresse kopieren"
},
"instance.worlds.create_shortcut": {
"message": "Verknüpfung erstellen"
},
"instance.worlds.dont_show_on_home": {
"message": "Nicht auf der Startseite anzeigen"
},
+192 -27
View File
@@ -11,6 +11,87 @@
"app.action-bar.hide-more-running-instances": {
"message": "Hide more running instances"
},
"app.action-bar.install.copied-details": {
"message": "Copied"
},
"app.action-bar.install.copy-details": {
"message": "Copy details"
},
"app.action-bar.install.dismiss": {
"message": "Dismiss"
},
"app.action-bar.install.open-instance": {
"message": "Open instance"
},
"app.action-bar.install.retry": {
"message": "Retry"
},
"app.action-bar.install.summary.app-closing": {
"message": "Canceled due to app closing"
},
"app.action-bar.install.summary.bad-modpack-file": {
"message": "Couldn't read modpack"
},
"app.action-bar.install.summary.canceled": {
"message": "Canceled"
},
"app.action-bar.install.summary.cleanup-incomplete": {
"message": "Cleanup didn't finish"
},
"app.action-bar.install.summary.content-download-failed": {
"message": "Couldn't download files"
},
"app.action-bar.install.summary.corrupt-download": {
"message": "Downloaded file is corrupt"
},
"app.action-bar.install.summary.could-not-save-files": {
"message": "Couldn't save files"
},
"app.action-bar.install.summary.download-failed": {
"message": "Download couldn't finish"
},
"app.action-bar.install.summary.instance-not-found": {
"message": "Instance couldn't be found"
},
"app.action-bar.install.summary.invalid-file-path": {
"message": "File path is invalid"
},
"app.action-bar.install.summary.invalid-modpack": {
"message": "Modpack data invalid"
},
"app.action-bar.install.summary.invalid-modpack-files": {
"message": "Modpack files have invalid metadata"
},
"app.action-bar.install.summary.java-setup-failed": {
"message": "Java setup couldn't finish"
},
"app.action-bar.install.summary.loader-setup-failed": {
"message": "Loader setup failed"
},
"app.action-bar.install.summary.local-data-error": {
"message": "Couldn't update local data"
},
"app.action-bar.install.summary.minecraft-setup-failed": {
"message": "Minecraft setup failed"
},
"app.action-bar.install.summary.modrinth-unreachable": {
"message": "Couldn't reach Modrinth"
},
"app.action-bar.install.summary.no-write-permission": {
"message": "No permission to write"
},
"app.action-bar.install.summary.pack-download-failed": {
"message": "Couldn't download pack"
},
"app.action-bar.install.summary.unexpected-error": {
"message": "Something went wrong"
},
"app.action-bar.install.unknown-instance": {
"message": "Unknown instance"
},
"app.action-bar.installs": {
"message": "Installs"
},
"app.action-bar.make-primary-instance": {
"message": "Make primary instance"
},
@@ -101,6 +182,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Show play time"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Automatically skips low-risk confirmations like duplicate modpack installs, normal content deletion, bulk updates, unlinking modpacks, and repair prompts. Dangerous warnings will still be shown."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Skip non-essential warnings"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Enables the ability to toggle the sidebar."
},
@@ -147,7 +234,7 @@
"message": "Discover servers"
},
"app.browse.hide-added-servers": {
"message": "Hide already added servers"
"message": "Hide servers already added"
},
"app.browse.project-type.modpacks": {
"message": "Modpacks"
@@ -161,12 +248,6 @@
"app.content-install.no-compatible-versions": {
"message": "No available versions match {compatibilityLabel}. Select a version to install anyway. Dependencies will not be installed automatically."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Installing modpack..."
},
"app.export-modal.description-placeholder": {
"message": "Enter modpack description..."
},
@@ -176,24 +257,72 @@
"app.export-modal.header": {
"message": "Export modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Include \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpack Name"
"message": "Modpack name"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack name"
},
"app.export-modal.select-files-label": {
"message": "Configure which files are included in this export"
},
"app.export-modal.version-number-label": {
"message": "Version number"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Downloading content"
},
"app.install.phase.downloading_minecraft": {
"message": "Downloading Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Downloading pack file"
},
"app.install.phase.extracting_overrides": {
"message": "Extracting overrides"
},
"app.install.phase.finalizing": {
"message": "Finalizing"
},
"app.install.phase.preparing_instance": {
"message": "Queued to install"
},
"app.install.phase.preparing_java": {
"message": "Preparing Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Downloading Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extracting Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Fetching Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparing Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validating Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Reading pack manifest"
},
"app.install.phase.resolving_loader": {
"message": "Resolving loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Resolving Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Resolving content"
},
"app.install.phase.rolling_back": {
"message": "Rolling back"
},
"app.install.phase.running_loader_processors": {
"message": "Running loader processors"
},
"app.instance.confirm-delete.admonition-body": {
"message": "All data for your instance will be permanently deleted, including your worlds, configs, and all installed content."
},
@@ -218,6 +347,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Downloading {current, number}/{total, number} projects..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finishing update..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Resolving versions..."
},
"app.instance.mods.content-type-project": {
"message": "project"
},
@@ -242,11 +380,17 @@
"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-modal.delete-button": {
"message": "Delete world"
},
"app.instance.worlds.delete-world-title": {
"message": "Are you sure you want to permanently delete this world?"
"app.instance.worlds.delete-world-modal.title": {
"message": "Delete world"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "This world will be permanently deleted from this instance. This action cannot be undone."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Deleting {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modded"
@@ -266,21 +410,21 @@
"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-modal.remove-button": {
"message": "Remove server"
},
"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-modal.title": {
"message": "Remove server"
},
"app.instance.worlds.remove-server-title": {
"message": "Are you sure you want to remove {name}?"
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "This server will be removed from your server list and from the in-game server list. You can add it again later if you know the address."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Removing {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"
},
@@ -320,12 +464,27 @@
"app.project.install-button.already-installed": {
"message": "This project is already installed"
},
"app.project.install-button.switch-version": {
"message": "Switch version"
},
"app.project.install-context.back-to-browse": {
"message": "Back to discover"
},
"app.project.install-context.install-content-to-instance": {
"message": "Install content to instance"
},
"app.project.version.all-versions": {
"message": "All versions"
},
"app.project.version.download-in-browser": {
"message": "Download in browser"
},
"app.project.version.installing": {
"message": "Installing"
},
"app.project.versions.already-installed": {
"message": "Already installed"
},
"app.settings.developer-mode-enabled": {
"message": "Developer mode enabled."
},
@@ -446,6 +605,9 @@
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chaos-cubed": {
"message": "Chaos Cubed"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
@@ -863,6 +1025,9 @@
"instance.worlds.copy_address": {
"message": "Copy address"
},
"instance.worlds.create_shortcut": {
"message": "Create shortcut"
},
"instance.worlds.dont_show_on_home": {
"message": "Don't show on Home"
},
+157 -28
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Descargando Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Descargando actualización"
},
"app.action-bar.downloads": {
"message": "Descargas"
},
"app.action-bar.hide-more-running-instances": {
"message": "Ocultar más instancias en ejecución"
},
"app.action-bar.install.dismiss": {
"message": "Descartar"
},
"app.action-bar.install.open-instance": {
"message": "Abrir instancia"
},
"app.action-bar.install.retry": {
"message": "Reintentar"
},
"app.action-bar.install.unknown-instance": {
"message": "Instancia desconocida"
},
"app.action-bar.installs": {
"message": "Instalaciones"
},
"app.action-bar.make-primary-instance": {
"message": "Hacer instancia principal"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Instancia principal"
},
"app.action-bar.reload-to-update": {
"message": "Reinicia para actualizar"
},
"app.action-bar.show-more-running-instances": {
"message": "Mostrar más instancias en ejecución"
},
"app.action-bar.stop-instance": {
"message": "Detener instancia"
},
"app.action-bar.update": {
"message": "Actualizar"
},
"app.action-bar.view-active-downloads": {
"message": "Ver descargas en curso"
},
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Mostrar tiempo de juego"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Omite automáticamente las confirmaciones de bajo riesgo, como las instalaciones duplicadas de paquetes de mods, la eliminación de contenido normal, las actualizaciones masivas, la desvinculación de paquetes de mods y las solicitudes de reparación. Las advertencias peligrosas seguirán mostrándose."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Omitir advertencias que no sean esenciales"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Permite mostrar u ocultar la barra lateral."
},
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "No hay versiones disponibles que coincidan con {compatibilityLabel}. Selecciona una versión para instalarla de todas formas. Las dependencias no se instalarán automáticamente."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Instalando modpack..."
},
"app.export-modal.description-placeholder": {
"message": "Introduce la descripción del modpack..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Exportar modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "¿Incluir \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nombre del modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nombre del modpack"
},
"app.export-modal.select-files-label": {
"message": "Configura que archivos incluir en esta exportación"
},
"app.export-modal.version-number-label": {
"message": "Número de la versión"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Descargando contenido"
},
"app.install.phase.downloading_minecraft": {
"message": "Descargando Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Descargando el archivo del paquete"
},
"app.install.phase.extracting_overrides": {
"message": "Extracción de anulaciones"
},
"app.install.phase.finalizing": {
"message": "Finalizando"
},
"app.install.phase.preparing_instance": {
"message": "Preparando la instancia"
},
"app.install.phase.preparing_java": {
"message": "Preparando Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Descargando Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Descomprimiendo Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Obteniendo Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparando Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validación de Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Leer el manifiesto del paquete"
},
"app.install.phase.resolving_loader": {
"message": "Resolviendo el cargador"
},
"app.install.phase.resolving_minecraft": {
"message": "Resolviendo Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Resolviendo el contenido"
},
"app.install.phase.rolling_back": {
"message": "Revertir"
},
"app.install.phase.running_loader_processors": {
"message": "Ejecutando los procesadores del cargador"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instancia"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Descargando {current, number}/{total, number} proyectos..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalizando la actualización..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Resolviendo versiones..."
},
"app.instance.mods.content-type-project": {
"message": "proyecto"
},
@@ -233,8 +314,17 @@
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-title": {
"message": "¿Estás seguro de que quieres eliminar este mundo de forma permanente?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Eliminar mundo"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Eliminar mundo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Este mundo se eliminará de forma permanente de esta instancia. Esta acción no se puede deshacer."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Eliminando {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Con mods"
@@ -254,15 +344,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "No hay servidores ni mundos añadidos"
},
"app.instance.worlds.remove-server-title": {
"message": "¿Estás seguro de que quieres eliminar {name}?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Eliminar servidor"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Eliminar servidor"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Este servidor se eliminará de tu lista de servidores y de la lista de servidores del juego. Puedes volver a agregarlo más adelante si conoces la dirección."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Se está eliminando {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"
},
@@ -303,7 +399,7 @@
"message": "Este proyecto ya está instalado"
},
"app.project.install-context.back-to-browse": {
"message": "Volver a descubrir contenido"
"message": "Volver al explorador"
},
"app.project.install-context.install-content-to-instance": {
"message": "Instalar contenido a la instancia"
@@ -336,7 +432,7 @@
"message": "Añadir skin"
},
"app.skins.add-button.drag-and-drop": {
"message": "Arrastrar y soltar"
"message": "Arrastra y suelta"
},
"app.skins.apply-button": {
"message": "Aplicar"
@@ -345,10 +441,10 @@
"message": "Eliminar skin"
},
"app.skins.delete-modal.description": {
"message": "Esto eliminará permanentemente el skin seleccionado. Esta acción no se puede deshacer."
"message": "Esto eliminará permanentemente la skin seleccionada. Esta acción no se puede deshacer."
},
"app.skins.delete-modal.title": {
"message": "¿Estás seguro de que quieres eliminar este skin?"
"message": "¿Estás seguro de que quieres eliminar esta skin?"
},
"app.skins.dropped-file-error.text": {
"message": "Error al leer el archivo arrastrado."
@@ -363,7 +459,7 @@
"message": "Añadir skin"
},
"app.skins.modal.add-title": {
"message": "Añadiendo un skin"
"message": "Añadiendo una skin"
},
"app.skins.modal.arm-style-section": {
"message": "Estilo de brazo"
@@ -405,7 +501,7 @@
"message": "Textura"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "¡Primero carga un skin!"
"message": "¡Primero carga una skin!"
},
"app.skins.preview.edit-button": {
"message": "Editar skin"
@@ -419,6 +515,12 @@
"app.skins.rate-limit.title": {
"message": "¡Más despacio!"
},
"app.skins.reorder-error.text": {
"message": "No se ha podido guardar el orden de las skins."
},
"app.skins.reorder-error.title": {
"message": "Error al reordenar las skins"
},
"app.skins.section.builders-and-biomes": {
"message": "Constructores y Biomas"
},
@@ -444,7 +546,7 @@
"message": "Monturas del Caos"
},
"app.skins.section.saved-skins": {
"message": "Skins guardados"
"message": "Skins guardadas"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
@@ -492,7 +594,7 @@
"message": "Descarga completada"
},
"app.update-popup.reload": {
"message": "Recargar"
"message": "Reinicia para actualizar"
},
"app.update-popup.title": {
"message": "Actualización disponible"
@@ -692,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "Nombre"
},
"instance.settings.tabs.general.update-channel": {
"message": "Canal de actualizaciones"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Las versiones estables, beta y alpha se mostrarán como actualizaciones disponibles. "
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Las versiones estables y beta serán mostradas como actualizaciones disponibles."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Estable"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Sólo las versiones estables serán mostradas como actualizaciones disponibles."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Selecciona el canal de actualizaciones"
},
"instance.settings.tabs.hooks": {
"message": "Hooks de inicio"
},
@@ -815,6 +941,9 @@
"instance.worlds.copy_address": {
"message": "Copiar dirección"
},
"instance.worlds.create_shortcut": {
"message": "Crear un acceso directo"
},
"instance.worlds.dont_show_on_home": {
"message": "No mostrar en la página de inicio"
},
@@ -882,7 +1011,7 @@
"message": "Proporcionado por el servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Solo se puede añadir mods que sean del lado del cliente a la instancia del servidor"
"message": "Solo se pueden añadir mods que sean del lado del cliente a la instancia del servidor"
},
"search.filter.locked.server-game-version.title": {
"message": "La versión del juego es proporcionada por el servidor"
+46 -34
View File
@@ -2,12 +2,18 @@
"app.action-bar.downloading-java": {
"message": "Descargando Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Descargando actualización"
},
"app.action-bar.downloads": {
"message": "Descargas"
},
"app.action-bar.hide-more-running-instances": {
"message": "Ocultar más instancias en ejecución"
},
"app.action-bar.install.dismiss": {
"message": "Descartar"
},
"app.action-bar.make-primary-instance": {
"message": "Establecer como instancia principal"
},
@@ -20,12 +26,18 @@
"app.action-bar.primary-instance": {
"message": "Instancia principal"
},
"app.action-bar.reload-to-update": {
"message": "Recargar para actualizar"
},
"app.action-bar.show-more-running-instances": {
"message": "Mostrar más instancias en ejecución"
},
"app.action-bar.stop-instance": {
"message": "Detener instancia"
},
"app.action-bar.update": {
"message": "Actualizar"
},
"app.action-bar.view-active-downloads": {
"message": "Mostrar descargas activas"
},
@@ -48,7 +60,7 @@
"message": "Tema de color"
},
"app.appearance-settings.default-landing-page.description": {
"message": "Cambia la página que se abre al iniciar el lanzador."
"message": "Cambia la página en la que el launcher inicia."
},
"app.appearance-settings.default-landing-page.home": {
"message": "Inicio"
@@ -152,12 +164,6 @@
"app.content-install.no-compatible-versions": {
"message": "No hay versiones disponibles que coincidan con {compatibilityLabel}. Selecciona una versión para instalarla de todas formas. Las dependencias no se instalarán automáticamente."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Instalando modpack..."
},
"app.export-modal.description-placeholder": {
"message": "Escribe la descripción del modpack..."
},
@@ -167,18 +173,12 @@
"app.export-modal.header": {
"message": "Exportar modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "¿Incluir \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nombre del modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nombre del modpack"
},
"app.export-modal.select-files-label": {
"message": "Configura que archivos se incluirán en esta instancia"
},
"app.export-modal.version-number-label": {
"message": "Número de versión"
},
@@ -233,12 +233,6 @@
"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"
},
@@ -257,21 +251,9 @@
"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"
},
@@ -428,6 +410,12 @@
"app.skins.rate-limit.title": {
"message": "¡Cálmate!"
},
"app.skins.reorder-error.text": {
"message": "No se ha podido guardar el orden de las skins."
},
"app.skins.reorder-error.title": {
"message": "Error al reordenar las skins"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders y Biomas"
},
@@ -501,7 +489,7 @@
"message": "Descarga completada"
},
"app.update-popup.reload": {
"message": "Recargar"
"message": "Recargar para actualizar"
},
"app.update-popup.title": {
"message": "Actualización disponible"
@@ -701,6 +689,30 @@
"instance.settings.tabs.general.name": {
"message": "Nombre"
},
"instance.settings.tabs.general.update-channel": {
"message": "Canal de actualizaciones"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Se mostrarán las versiones estables, beta y alfa como actualizaciones disponibles."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Se mostrarán las versiones estables y beta como actualizaciones disponibles."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Estable"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Solo se mostrarán las versiones estables como actualizaciones disponibles."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Seleccionar canal de actualizaciones"
},
"instance.settings.tabs.hooks": {
"message": "Hooks de lanzamiento"
},
@@ -882,7 +894,7 @@
"message": "Versión del juego proporciona por la instancia"
},
"search.filter.locked.instance-loader.title": {
"message": "Cargador proporcionado por la instancia"
"message": "Loader proporcionado por la instancia"
},
"search.filter.locked.instance.sync": {
"message": "Sincronizar con la instancia"
@@ -897,7 +909,7 @@
"message": "La versión del juego es proporcionada por el servidor"
},
"search.filter.locked.server-loader.title": {
"message": "El cargador lo proporciona el servidor"
"message": "Loader proporcionado por el servidor"
},
"unknown-pack-warning-modal.body": {
"message": "Un archivo solo es revisado si es subido a Modrinth, independientemente de su formato de archivo (incluyendo .mrpack)."
+295 -25
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Ladataan Java-versiota {version}"
},
"app.action-bar.downloading-update": {
"message": "Ladataan päivitystä"
},
"app.action-bar.downloads": {
"message": "Lataukset"
},
"app.action-bar.hide-more-running-instances": {
"message": "Piilota enemmän käynnissä olevia instansseja"
},
"app.action-bar.install.dismiss": {
"message": "Hylkää"
},
"app.action-bar.install.open-instance": {
"message": "Avaa instanssi"
},
"app.action-bar.install.retry": {
"message": "Yritä uudelleen"
},
"app.action-bar.install.unknown-instance": {
"message": "Tuntematon instanssi"
},
"app.action-bar.installs": {
"message": "Asennukset"
},
"app.action-bar.make-primary-instance": {
"message": "Aseta ensisijaiseksi instanssiksi"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Ensisijainen instanssi"
},
"app.action-bar.reload-to-update": {
"message": "Lataa uudelleen päivittääksesi"
},
"app.action-bar.show-more-running-instances": {
"message": "Näytä enemmän käynnissä olevia instansseja"
},
"app.action-bar.stop-instance": {
"message": "Pysäytä instanssi"
},
"app.action-bar.update": {
"message": "Päivitä"
},
"app.action-bar.view-active-downloads": {
"message": "Näytä aktiiviset lataukset"
},
@@ -86,6 +110,18 @@
"app.appearance-settings.select-option": {
"message": "Valitse vaihtoehto"
},
"app.appearance-settings.show-play-time.description": {
"message": "Näyttää, kuinka paljon aikaa olet käyttänyt instanssin pelaamiseen."
},
"app.appearance-settings.show-play-time.title": {
"message": "Näyttää peliaika"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Ohittaa automaattisesti vähäriskiset vahvistukset, kuten modpackien kaksoisasennukset, tavallisen sisällön poistamisen, massapäivitykset, modpackien linkityksen poistamisen ja korjauskehotteet. Vaaralliset varoitukset näytetään silti."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Ohita ei olennaiset varoitukset"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Laittaa päälle mahdollisuuden kytkeä sivupalkkia."
},
@@ -143,11 +179,8 @@
"app.browse.server.installing": {
"message": "Asennetaan"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Asennetaan modipakettia..."
"app.content-install.no-compatible-versions": {
"message": "Yhtään versiota ei ole saatavilla yhteensopivuudella {compatibilityLabel}. Valitse asennettava versio siitä huolimatta. Riippuvuuksia ei asenneta automaattisesti."
},
"app.export-modal.description-placeholder": {
"message": "Lisää modipaketin kuvaus..."
@@ -158,24 +191,72 @@
"app.export-modal.header": {
"message": "Vie modipaketti"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Sisällytä \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modipaketin nimi"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modipaketin nimi"
},
"app.export-modal.select-files-label": {
"message": "Muuta mitkä tiedostot sisältyvät tähän vientiin"
},
"app.export-modal.version-number-label": {
"message": "Versio numero"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Ladataan sisältöä"
},
"app.install.phase.downloading_minecraft": {
"message": "Ladataan Minecraftia"
},
"app.install.phase.downloading_pack_file": {
"message": "Ladataan paketti tiedostoja"
},
"app.install.phase.extracting_overrides": {
"message": "Puretaan ohituksia"
},
"app.install.phase.finalizing": {
"message": "Viimeistellään"
},
"app.install.phase.preparing_instance": {
"message": "Valmistellaan instanssia"
},
"app.install.phase.preparing_java": {
"message": "Valmistellaan Javaa"
},
"app.install.phase.preparing_java.downloading": {
"message": "Ladataan Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Puretaan Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Haetaan Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Valmistellaan Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Tarkistetaan Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Luetaan paketin manifestia"
},
"app.install.phase.resolving_loader": {
"message": "Ratkaistaan lataajaa"
},
"app.install.phase.resolving_minecraft": {
"message": "Ratkaistaan Minecraftia"
},
"app.install.phase.resolving_pack": {
"message": "Ratkaistaan sisältöä"
},
"app.install.phase.rolling_back": {
"message": "Palautetaan muutoksia"
},
"app.install.phase.running_loader_processors": {
"message": "Suoritetaan lataajan käsittelijöitä"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Kaikki sinun instanssisi data poistetaan pysyvästi, sisältäen maailmasi, konfiguraatiosi ja asennetun sisällön."
},
@@ -200,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instanssi"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Ladataan {current, number}/{total, number} projekteja..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Viimeistellään päivitystä..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Selvitetään versioita..."
},
"app.instance.mods.content-type-project": {
"message": "projekti"
},
@@ -224,11 +314,17 @@
"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-modal.delete-button": {
"message": "Poista maailma"
},
"app.instance.worlds.delete-world-title": {
"message": "Oletko varma että haluat pysyvästi poistaa tämän maailman?"
"app.instance.worlds.delete-world-modal.title": {
"message": "Poista maailma"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Tämä maailma poistetaan pysyvästi tästä instanssista. Tätä toimintoa ei voi kumota."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Poistetaan {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modattu"
@@ -248,21 +344,21 @@
"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-modal.remove-button": {
"message": "Poista palvelin"
},
"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-modal.title": {
"message": "Poista palvelin"
},
"app.instance.worlds.remove-server-title": {
"message": "Oletko varma ethaluat poistaa {name}?"
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Tämä palvelin poistetaan palvelinluettelostasi ja pelin sisäisespalvelinluettelosta. Voit lisätä sen myöhemmin uudelleen, jos tiedät osoitteen."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Poistetaan {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"
},
@@ -332,6 +428,153 @@
"app.settings.tabs.resource-management": {
"message": "Resurssien hallinta"
},
"app.skins.add-button": {
"message": "Lisää ulkoasu"
},
"app.skins.add-button.drag-and-drop": {
"message": "Raahaa ja pudota"
},
"app.skins.apply-button": {
"message": "Käytä"
},
"app.skins.delete-button": {
"message": "Poista ulkoasu"
},
"app.skins.delete-modal.description": {
"message": "Tämä poistaa valitun skinin pysyvästi. Tätä toimintoa ei voi kumota."
},
"app.skins.delete-modal.title": {
"message": "Haluatko varmasti poistaa ulkoasun?"
},
"app.skins.dropped-file-error.text": {
"message": "Tiedoston lukeminen epäonnistui."
},
"app.skins.dropped-file-error.title": {
"message": "Virhe prosessoidessa tiedostoa"
},
"app.skins.edit-button": {
"message": "Muokkaa ulkoasua"
},
"app.skins.modal.add-skin-button": {
"message": "Lisää ulkoasu"
},
"app.skins.modal.add-title": {
"message": "Lisätään ulkoasua"
},
"app.skins.modal.arm-style-section": {
"message": "Käsien tyyli"
},
"app.skins.modal.arm-style-slim": {
"message": "Ohut"
},
"app.skins.modal.arm-style-wide": {
"message": "Paksu"
},
"app.skins.modal.cape-fallback-name": {
"message": "Viitta"
},
"app.skins.modal.cape-section": {
"message": "Viitta"
},
"app.skins.modal.edit-title": {
"message": "Muokataan ulkoasua"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "Tee muokkaus ulkoasuun ensin!"
},
"app.skins.modal.no-cape-tooltip": {
"message": "Ei viittaa"
},
"app.skins.modal.none-cape-option": {
"message": "Ei mitään"
},
"app.skins.modal.replace-texture-button": {
"message": "Korvaa tekstuuri"
},
"app.skins.modal.save-skin-button": {
"message": "Tallenna ulkoasu"
},
"app.skins.modal.saving-tooltip": {
"message": "Tallennetaan..."
},
"app.skins.modal.texture-section": {
"message": "Tekstuuri"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "Lataa ulkoasu ensin!"
},
"app.skins.preview.edit-button": {
"message": "Muokkaa ulkoasua"
},
"app.skins.previewing-badge": {
"message": "Esikatsellaan"
},
"app.skins.rate-limit.text": {
"message": "Vaihdat ulkoasua liian usein. Mojangin palvelimet ovat estäneet uudet pyynnöt väliaikaisesti. Yritä hetken kuluttua uudelleen."
},
"app.skins.rate-limit.title": {
"message": "Hidasta vähän!"
},
"app.skins.reorder-error.text": {
"message": "Ulkoasujen järjestystä ei voitu tallentaa."
},
"app.skins.reorder-error.title": {
"message": "Skinien järjestyksen muuttaminen epäonnistui"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
"app.skins.section.default-skins": {
"message": "Oletus ulkoasut"
},
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
"app.skins.section.modrinth-pride.tooltip": {
"message": "Sait nämä skinit lahjoittamalla Modrinthin Pride-keräykseen Pride-kuukauden aikana."
},
"app.skins.section.mounts-of-mayhem": {
"message": "Sekasorron ratsut"
},
"app.skins.section.saved-skins": {
"message": "Tallennetut ulkoasut"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "The Copper Age"
},
"app.skins.section.the-garden-awakens": {
"message": "The Garden Awakens"
},
"app.skins.section.tiny-takeover": {
"message": "Pienten vallankumous"
},
"app.skins.sign-in.button": {
"message": "Kirjaudu sisään"
},
"app.skins.sign-in.description": {
"message": "Kirjaudu Minecraft-tilillesi käyttääksesi Modrinth-sovelluksen ulkoasujen hallintaominaisuuksia."
},
"app.skins.sign-in.rinthbot-alt": {
"message": "Innostunut Modrinth Botti"
},
"app.skins.sign-in.title": {
"message": "Kirjaudu sisään"
},
"app.skins.title": {
"message": "Skinivalinta"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App versio {version} on ladattu. Voit käynnistää sovelluksen uudelleen päivittääksesi heti tai antaa päivityksen asentua automaattisesti, kun suljet Modrinth Appin."
},
@@ -351,7 +594,7 @@
"message": "Lataus valmis"
},
"app.update-popup.reload": {
"message": "Lataa uudelleen"
"message": "Lataa uudelleen päivittääksesi"
},
"app.update-popup.title": {
"message": "Päivitys saatavilla"
@@ -551,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "Nimi"
},
"instance.settings.tabs.general.update-channel": {
"message": "Päivitys kanava"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Release-, beta- ja alpha-versiot näytetään saatavilla olevina päivityksinä."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Release- ja beta-versiot näytetään saatavilla olevina päivityksinä."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Julkaisu"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Vain release-versiot näytetään saatavilla olevina päivityksinä."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Valitse päivitys kanava"
},
"instance.settings.tabs.hooks": {
"message": "Aloitustoiminnot"
},
@@ -674,6 +941,9 @@
"instance.worlds.copy_address": {
"message": "Kopioi osoite"
},
"instance.worlds.create_shortcut": {
"message": "Luo pikakuvake"
},
"instance.worlds.dont_show_on_home": {
"message": "Älä näytä koti näkymässä"
},
+18 -33
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Dina-download ang Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Dina-download ang update"
},
"app.action-bar.downloads": {
"message": "Mga downloads"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Pangunahing instansiya"
},
"app.action-bar.reload-to-update": {
"message": "Mag-reload upang ma-update"
},
"app.action-bar.show-more-running-instances": {
"message": "Ipakita ang mas marami pang tumatakbong instansiya"
},
"app.action-bar.stop-instance": {
"message": "Itigil ang instansiya"
},
"app.action-bar.update": {
"message": "Mag-update"
},
"app.action-bar.view-active-downloads": {
"message": "Tignan ang mga active downloads"
},
@@ -86,6 +95,9 @@
"app.appearance-settings.select-option": {
"message": "Pumili ng opsiyon"
},
"app.appearance-settings.show-play-time.description": {
"message": "Magpapakita ang katagal ng iyong paglalaro sa isang instansiya."
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Pagpapagana na mata-toggle ang sidebar."
},
@@ -143,12 +155,6 @@
"app.browse.server.installing": {
"message": "Ini-install"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Ini-install ang modpack..."
},
"app.export-modal.description-placeholder": {
"message": "Ilagay ang paglalarawan ng modpack..."
},
@@ -158,18 +164,12 @@
"app.export-modal.header": {
"message": "Iluwas ang modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Salihin ang \"{file}\"?"
},
"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": "Isaayos kung anong mga file ang isasali sa pagluwas"
},
"app.export-modal.version-number-label": {
"message": "Numero ng bersiyon"
},
@@ -224,12 +224,6 @@
"app.instance.worlds.browse-servers": {
"message": "Mag-browse ng servers"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' ay **permanenteng mabubura**, at walang paraan upang maibalik muli."
},
"app.instance.worlds.delete-world-title": {
"message": "Sigurado ka bang gusto mong permanenteng burahin ang world na ito?"
},
"app.instance.worlds.filter-modded": {
"message": "Modded"
},
@@ -248,21 +242,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Walang servers o worlds ang nalalagay"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' ay matatangal sa iyong listahan, pati sa loob ng laro, at walang paraan upang maibalik ito."
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) ay matatangal sa iyong listahan, pati sa loob ng laro, at walang paraan upang maibalik ito."
},
"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"
},
@@ -377,9 +359,6 @@
"app.update-popup.download-complete": {
"message": "Nakumpleto ang pagdownload"
},
"app.update-popup.reload": {
"message": "Mag-reload"
},
"app.update-popup.title": {
"message": "May bagong update"
},
@@ -578,6 +557,12 @@
"instance.settings.tabs.general.name": {
"message": "Pangalan"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.hooks": {
"message": "Mga launch hook"
},
+150 -21
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Téléchargement de Java {version} en cours"
},
"app.action-bar.downloading-update": {
"message": "Téléchargement de la mise à jour"
},
"app.action-bar.downloads": {
"message": "Téléchargements"
},
"app.action-bar.hide-more-running-instances": {
"message": "Masquer plus d'instances en exécution"
},
"app.action-bar.install.dismiss": {
"message": "Rejeter"
},
"app.action-bar.install.open-instance": {
"message": "Ouvrir l'instance"
},
"app.action-bar.install.retry": {
"message": "Réessayer"
},
"app.action-bar.install.unknown-instance": {
"message": "Instance inconnue"
},
"app.action-bar.installs": {
"message": "Installations"
},
"app.action-bar.make-primary-instance": {
"message": "Définir en tante qu'instance première"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Instance première"
},
"app.action-bar.reload-to-update": {
"message": "Recharger pour mettre à jour"
},
"app.action-bar.show-more-running-instances": {
"message": "Montrer plus d'instances en exécution"
},
"app.action-bar.stop-instance": {
"message": "Arrêter l'instance"
},
"app.action-bar.update": {
"message": "Mettre à jour"
},
"app.action-bar.view-active-downloads": {
"message": "Voir les téléchargements actifs"
},
@@ -92,8 +116,14 @@
"app.appearance-settings.show-play-time.title": {
"message": "Afficher le temps de jeu"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Ignore automatiquement les confirmations à faible risque, comme l'installation de modpacks en double, la suppression de contenu standard, les mises à jour groupées, la dissociation des modpacks et les demandes de réparation. Les avertissements concernant les actions dangereuses continueront d'être affichés."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Cacher les alertes non essentielles"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Permet d'ouvrir de fermer la barre latérale."
"message": "Permet d'ouvrir ou de fermer la barre latérale."
},
"app.appearance-settings.toggle-sidebar.title": {
"message": "Tiroir latéral"
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Aucune version disponible ne correspond à {compatibilityLabel}. Sélectionnez une version pour l'installer malgré tout. Les dépendances ne seront pas installées automatiquement."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Installation du modpack..."
},
"app.export-modal.description-placeholder": {
"message": "Saisir la description du modpack..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Exporter le modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Inclure « {file} » ?"
},
"app.export-modal.modpack-name-label": {
"message": "Nom du modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nom du modpack"
},
"app.export-modal.select-files-label": {
"message": "Configurez quels fichiers sont inclus dans cette exportation"
},
"app.export-modal.version-number-label": {
"message": "Numéro de version"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Téléchargement du contenu"
},
"app.install.phase.downloading_minecraft": {
"message": "Téléchargement de Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Téléchargement du fichier de pack"
},
"app.install.phase.extracting_overrides": {
"message": "Extraction des fichiers personnalisés"
},
"app.install.phase.finalizing": {
"message": "Finalisation"
},
"app.install.phase.preparing_instance": {
"message": "Préparation de l'instance"
},
"app.install.phase.preparing_java": {
"message": "Préparation de Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Téléchargement de Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extraction de Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Récupération de Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Préparation de Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validation de Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Lecture du manifeste du pack"
},
"app.install.phase.resolving_loader": {
"message": "Résolution du loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Résolution de Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Résolution du contenu"
},
"app.install.phase.rolling_back": {
"message": "Annulation"
},
"app.install.phase.running_loader_processors": {
"message": "Exécution des processeurs du chargeur"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Toutes les données pour votre instance seront supprimées à jamais, y compris vos mondes, vos configurations, et le contenu installé."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instance"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Téléchargement de {current, number}/{total, number} projets..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalisation de la mise à jour..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Résolution des versions..."
},
"app.instance.mods.content-type-project": {
"message": "projet"
},
@@ -233,8 +314,17 @@
"app.instance.worlds.browse-servers": {
"message": "Parcourir des serveurs"
},
"app.instance.worlds.delete-world-title": {
"message": "Êtes-vous sûr·e de vouloir supprimer ce monde pour toujours ?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Supprimer le monde"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Supprimer le monde"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Ce monde sera définitivement supprimé de cette instance. Cette action est irréversible."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Suppression de {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Moddé"
@@ -254,15 +344,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Aucun serveur ou monde ajoutés"
},
"app.instance.worlds.remove-server-title": {
"message": "Êtes-vous sûr·e de vouloir retirer {name} ?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Enlever le serveur"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Enlever le serveur"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Ce serveur sera enlevé de votre liste de serveur et de la liste de serveur en jeu. Vous pourrez le rajouter plus tard si vous connaissez son adresse."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Retrait de {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"
},
@@ -419,6 +515,12 @@
"app.skins.rate-limit.title": {
"message": "Du calme !"
},
"app.skins.reorder-error.text": {
"message": "L'ordre des skins n'a pas pu être sauvegardé."
},
"app.skins.reorder-error.title": {
"message": "Impossible de réorganiser les skins"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -492,7 +594,7 @@
"message": "Téléchargement terminé"
},
"app.update-popup.reload": {
"message": "Recharger"
"message": "Recharger pour mettre à jour"
},
"app.update-popup.title": {
"message": "Mise à jour disponible"
@@ -689,6 +791,30 @@
"instance.settings.tabs.general.name": {
"message": "Nom"
},
"instance.settings.tabs.general.update-channel": {
"message": "Canal de mise à jour"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Les versions stables, bêta et alpha seront affichées comme mises à jour disponibles."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Bêta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Les versions stables et bêta seront affichées comme mises à jour disponibles."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Stable"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Seules les versions stables seront affichées comme mises à jour disponibles."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Sélectionner le canal de mise à jour"
},
"instance.settings.tabs.hooks": {
"message": "Crochets de lancement"
},
@@ -812,6 +938,9 @@
"instance.worlds.copy_address": {
"message": "Copier l'adresse"
},
"instance.worlds.create_shortcut": {
"message": "Créer un raccourci"
},
"instance.worlds.dont_show_on_home": {
"message": "Ne pas montrer dans l'Accueil"
},
+18 -21
View File
@@ -1,7 +1,19 @@
{
"app.action-bar.downloading-java": {
"message": "מוריד את Java {version}"
},
"app.action-bar.downloading-update": {
"message": "מוריד עדכון"
},
"app.action-bar.downloads": {
"message": "הורדות"
},
"app.action-bar.hide-more-running-instances": {
"message": "החבא יותר התקנים רצים"
},
"app.action-bar.make-primary-instance": {
"message": "הפוך למקרה ראשי"
},
"app.action-bar.offline": {
"message": "לא מקוון"
},
@@ -11,6 +23,9 @@
"app.appearance-settings.advanced-rendering.description": {
"message": "מאפשר עיבוד מתקדם כגון אפקטים טשטוש העלולים לגרום לבעיות ביצועים ללא עיבוד מואץ לחומרה."
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "חזרה לעולמות"
},
"app.auth-servers.unreachable.body": {
"message": "ייתכן ששרתי האימות של Minecraft מושבתים כרגע. יש לבדוק את חיבור האינטרנט שלך ולנסות שוב מאוחר יותר."
},
@@ -104,12 +119,6 @@
"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": "עם מודים"
},
@@ -128,21 +137,9 @@
"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": "תוכן נדרש"
},
@@ -221,9 +218,6 @@
"app.update-popup.download-complete": {
"message": "הורדה הושלמה"
},
"app.update-popup.reload": {
"message": "רענן"
},
"app.update-popup.title": {
"message": "עדכון זמין"
},
@@ -254,6 +248,9 @@
"friends.action.add-friend": {
"message": "הוספת חבר"
},
"friends.action.view-friend-requests": {
"message": "{count, plural, one {בקשת חברות אחת} other {# בקשות חברות}}"
},
"friends.add-friend.submit": {
"message": "שליחת בקשת חברות"
},
+50 -32
View File
@@ -2,12 +2,21 @@
"app.action-bar.downloading-java": {
"message": "A Java {version} letöltése"
},
"app.action-bar.downloading-update": {
"message": "Frissítés letöltése"
},
"app.action-bar.downloads": {
"message": "Letöltések"
},
"app.action-bar.hide-more-running-instances": {
"message": "További futó példányok elrejtése"
},
"app.action-bar.install.retry": {
"message": "Újra"
},
"app.action-bar.install.unknown-instance": {
"message": "Ismeretlen profil"
},
"app.action-bar.make-primary-instance": {
"message": "Beállítás elsődleges példányként"
},
@@ -20,12 +29,18 @@
"app.action-bar.primary-instance": {
"message": "Elsődleges példány"
},
"app.action-bar.reload-to-update": {
"message": "Töltsd újra a frissítéshez"
},
"app.action-bar.show-more-running-instances": {
"message": "További futó pédányok megjelenítése"
},
"app.action-bar.stop-instance": {
"message": "Példány leállítása"
},
"app.action-bar.update": {
"message": "Frissítés"
},
"app.action-bar.view-active-downloads": {
"message": "Aktív letöltések megtekintése"
},
@@ -69,7 +84,7 @@
"message": "A kezdőlap „Folytasd itt” részében a legújabb világok is megtalálhatók."
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "„Folytasd itt” a világokat is"
"message": "Visszaugrás a világokba"
},
"app.appearance-settings.minimize-launcher.description": {
"message": "Minimalizáld az indítót a Minecraft indításakor."
@@ -152,12 +167,6 @@
"app.content-install.no-compatible-versions": {
"message": "Nincs a(z) {compatibilityLabel} verziónak megfelelő elérhető verzió. Válassz ki egy verziót, ha mégis telepíteni szeretnéd. A függőségek nem települnek automatikusan."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Modcsomag telepítése..."
},
"app.export-modal.description-placeholder": {
"message": "Írd be a modcsomag leírását..."
},
@@ -167,24 +176,21 @@
"app.export-modal.header": {
"message": "Modcsomag exportálása"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Tartalmaz „{file}” fájlokat?"
},
"app.export-modal.modpack-name-label": {
"message": "A modcsomag neve"
},
"app.export-modal.modpack-name-placeholder": {
"message": "A modcsomag neve"
},
"app.export-modal.select-files-label": {
"message": "Állítsd be, hogy mely fájlok kerüljenek bele az exportba"
},
"app.export-modal.version-number-label": {
"message": "Verziószám"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.resolving_loader": {
"message": ""
},
"app.instance.confirm-delete.admonition-body": {
"message": "Az profilodhoz tartozó összes adat véglegesen törlődik, beleértve a világokat, a beállításokat és az összes telepített tartalmat."
},
@@ -233,12 +239,6 @@
"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"
},
@@ -257,21 +257,9 @@
"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"
},
@@ -281,6 +269,9 @@
"app.modal.install-to-play.install-button": {
"message": "Telepítés"
},
"app.modal.install-to-play.mod-count": {
"message": "{count} mod"
},
"app.modal.install-to-play.required-modpack": {
"message": "Szükséges modcsomag"
},
@@ -498,7 +489,7 @@
"message": "Sikeres letöltés"
},
"app.update-popup.reload": {
"message": "Frissítés"
"message": "Töltsd újra a frissítéshez"
},
"app.update-popup.title": {
"message": "Frissítés elérhető"
@@ -530,6 +521,9 @@
"friends.action.add-friend": {
"message": "Barát hozzáadása"
},
"friends.action.view-friend-requests": {
"message": "{count} barát {count, plural, other {kérelem}}"
},
"friends.add-friend.submit": {
"message": "Barátkérelem elküldése"
},
@@ -695,6 +689,30 @@
"instance.settings.tabs.general.name": {
"message": "Név"
},
"instance.settings.tabs.general.update-channel": {
"message": "Frissítési csatorna"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "A kiadás, béta és az alfa verziók is meg fognak jelenni az elérhető frissítések között."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Béta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "A kiadás és a béta verziók is meg fognak jelenni az elérhető frissítések között."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Kiadás"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Csak a release verziók fognak megjelenni az elérhető frissítések között."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Válaszd ki a frissítési csatornát"
},
"instance.settings.tabs.hooks": {
"message": "Indítási horgok"
},
+54 -30
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Mengunduh Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Mengunduh pembaruan"
},
"app.action-bar.downloads": {
"message": "Unduhan"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Instans utama"
},
"app.action-bar.reload-to-update": {
"message": "Muat ulang untuk memperbarui"
},
"app.action-bar.show-more-running-instances": {
"message": "Tampilkan lebih banyak instans yang sedang berjalan"
},
"app.action-bar.stop-instance": {
"message": "Hentikan instans"
},
"app.action-bar.update": {
"message": "Perbarui"
},
"app.action-bar.view-active-downloads": {
"message": "Lihat pengunduhan berlangsung"
},
@@ -86,6 +95,12 @@
"app.appearance-settings.select-option": {
"message": "Pilih opsi"
},
"app.appearance-settings.show-play-time.description": {
"message": "Menampilkan seberapa lama Anda memainkan sebuah instans."
},
"app.appearance-settings.show-play-time.title": {
"message": "Tampilkan waktu bermain"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Menghidupkan kemampuan untuk menghidupkan dan mematikan bilah sisi."
},
@@ -143,11 +158,8 @@
"app.browse.server.installing": {
"message": "Memasang"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Memasang paket mod..."
"app.content-install.no-compatible-versions": {
"message": "Tidak ada versi tersedia yang cocok untuk {compatibilityLabel}. Anda tetap dapat memilih versi yang akan dipasang. Dependensi tidak akan dipasang secara otomatis."
},
"app.export-modal.description-placeholder": {
"message": "Masukkan deskripsi paket mod..."
@@ -158,18 +170,12 @@
"app.export-modal.header": {
"message": "Ekspor paket mod"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Sertakan \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nama Paket Mod"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nama paket mod"
},
"app.export-modal.select-files-label": {
"message": "Konfigurasikan berkas mana yang disertakan dalam pengeksporan ini"
},
"app.export-modal.version-number-label": {
"message": "Nomor versi"
},
@@ -224,12 +230,6 @@
"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"
},
@@ -248,21 +248,9 @@
"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"
},
@@ -272,6 +260,9 @@
"app.modal.install-to-play.install-button": {
"message": "Pasang"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Paket mod yang diperlukan"
},
@@ -416,6 +407,12 @@
"app.skins.rate-limit.title": {
"message": "Pelan-pelan!"
},
"app.skins.reorder-error.text": {
"message": "Urutan rupa Anda tidak dapat disimpan."
},
"app.skins.reorder-error.title": {
"message": "Gagal mengurut ulang rupa"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -489,7 +486,7 @@
"message": "Selesai mengunduh"
},
"app.update-popup.reload": {
"message": "Muat ulang"
"message": "Muat ulang untuk memperbarui"
},
"app.update-popup.title": {
"message": "Pembaruan tersedia"
@@ -521,6 +518,9 @@
"friends.action.add-friend": {
"message": "Tambah teman"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, other {permintaan}} teman"
},
"friends.add-friend.submit": {
"message": "Kirim permintaan teman"
},
@@ -686,6 +686,30 @@
"instance.settings.tabs.general.name": {
"message": "Nama"
},
"instance.settings.tabs.general.update-channel": {
"message": "Perbarui kanal"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Versi rilisan, beta, dan alpha akan ditampilkan sebagai pembaruan yang tersedia."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Versi beta dan alpha akan ditampilkan sebagai pembaruan yang tersedia."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Rilisan"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Hanya versi rilisan yang akan ditampilkan sebagai pembaruan yang tersedia."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Pilih kanal pembaruan"
},
"instance.settings.tabs.hooks": {
"message": "Luncurkan kait"
},
+173 -44
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Scaricando l'aggiornamento"
},
"app.action-bar.downloads": {
"message": "Download"
},
"app.action-bar.hide-more-running-instances": {
"message": "Mostra meno istanze in esecuzione"
},
"app.action-bar.install.dismiss": {
"message": "Ignora"
},
"app.action-bar.install.open-instance": {
"message": "Apri istanza"
},
"app.action-bar.install.retry": {
"message": "Riprova"
},
"app.action-bar.install.unknown-instance": {
"message": "Istanza sconosciuta"
},
"app.action-bar.installs": {
"message": "Installazioni"
},
"app.action-bar.make-primary-instance": {
"message": "Rendi primaria"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Primaria"
},
"app.action-bar.reload-to-update": {
"message": "Ricarica per aggiornare"
},
"app.action-bar.show-more-running-instances": {
"message": "Mostra più istanze in esecuzione"
},
"app.action-bar.stop-instance": {
"message": "Ferma l'istanza"
},
"app.action-bar.update": {
"message": "Aggiorna"
},
"app.action-bar.view-active-downloads": {
"message": "Mostra i download attivi"
},
@@ -90,7 +114,13 @@
"message": "Mostra quanto tempo hai speso giocando a un'istanza."
},
"app.appearance-settings.show-play-time.title": {
"message": "Mostra tempo di gioco"
"message": "Mostra le ore di gioco"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Salta automaticamente le conferme a basso rischio, come l'installazione di pacchetti di mod duplicati, la normale eliminazione di contenuti, gli aggiornamenti simultanei, lo scollegare i pacchetti di mod e le richieste di riparazione. Le avvertenze verranno comunque mostrate."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Salta le conferme poco importanti"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Mostra o nascondi la barra laterale."
@@ -144,19 +174,13 @@
"message": "Pacchetti di mod"
},
"app.browse.server-instance-content-warning": {
"message": "Aggiungere dei contenuti potrebbe causare problemi entrando nel server. Inoltre, qualsiasi contenuto aggiunto sarà perso a ogni aggiornamento dei contenuti dell'istanza."
"message": "Aggiungere dei contenuti potrebbe causare problemi entrando nel server. Inoltre, qualsiasi contenuto aggiunto sarà perso quando aggiorni i contenuti dell'istanza."
},
"app.browse.server.installing": {
"message": "Installazione"
},
"app.content-install.no-compatible-versions": {
"message": "Nessuna versione disponibile corrisponde a {compatibilityLabel}. Seleziona una versione da installare comunque. Le dipendenze non verranno scaricate automaticamente."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Installando il pacchetto..."
"message": "Nessuna versione disponibile soddisfa {compatibilityLabel}. Puoi selezionare una versione da installare comunque. Le dipendenze non verranno scaricate automaticamente."
},
"app.export-modal.description-placeholder": {
"message": "Inserisci descrizione del pacchetto..."
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Esporta pacchetto"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Includere \"{file}\" nell'esportazione?"
},
"app.export-modal.modpack-name-label": {
"message": "Nome del pacchetto"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nome del pacchetto"
},
"app.export-modal.select-files-label": {
"message": "Seleziona i file da includere in questa esportazione"
},
"app.export-modal.version-number-label": {
"message": "Numero di versione"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Download dei contenuti"
},
"app.install.phase.downloading_minecraft": {
"message": "Download di Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Download del pacchetto"
},
"app.install.phase.extracting_overrides": {
"message": "Estrazione degli override"
},
"app.install.phase.finalizing": {
"message": "Finalizzazione"
},
"app.install.phase.preparing_instance": {
"message": "Preparazione dell'istanza"
},
"app.install.phase.preparing_java": {
"message": "Preparazione di Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Download di Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Estrazione di Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Recupero di Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparazione di Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validazione di Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Lettura del manifesto"
},
"app.install.phase.resolving_loader": {
"message": "Risoluzione del loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Risoluzione di Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Risoluzione dei contenuti"
},
"app.install.phase.rolling_back": {
"message": "Tornando indietro"
},
"app.install.phase.running_loader_processors": {
"message": "Esecuzione dei processori del loader"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Tutti i dati della tua istanza verranno eliminati permanentemente, inclusi i tuoi mondi, configurazioni e tutti i contenuti installati."
},
@@ -209,15 +281,21 @@
"app.instance.modpack-already-installed.instance": {
"message": "Istanza"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} progetti scaricati..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalizzando l'aggiornamento..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Risolvendo le versioni..."
},
"app.instance.mods.content-type-project": {
"message": "progetto"
},
"app.instance.mods.project-was-added": {
"message": "\"{name}\" è stato aggiunto"
},
"app.instance.mods.projects-were-added": {
"message": "{count} progetti aggiunti"
},
"app.instance.mods.share-text": {
"message": "Guarda le mod che uso nel mio pacchetto!"
},
@@ -233,8 +311,17 @@
"app.instance.worlds.browse-servers": {
"message": "Esplora i server"
},
"app.instance.worlds.delete-world-title": {
"message": "Vuoi davvero eliminare questo mondo per sempre?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Elimina mondo"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Eliminare il mondo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Questo mondo verrà eliminato permanentemente da questa istanza. Questa azione non può essere annullata."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Eliminare {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Moddato"
@@ -254,15 +341,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Nessun server o mondo aggiunto"
},
"app.instance.worlds.remove-server-title": {
"message": "Vuoi davvero rimuovere {name}?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Rimuovi server"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "Rimuovere il server"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Questo server verrà rimosso dalla tua lista di server nell'app e nel gioco. Potrai aggiungerlo nuovamente se conosci l'indirizzo."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "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"
},
@@ -272,6 +365,9 @@
"app.modal.install-to-play.install-button": {
"message": "Installa"
},
"app.modal.install-to-play.mod-count": {
"message": "{count} mod"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pacchetto di mod richiesto"
},
@@ -333,7 +429,7 @@
"message": "Nuova skin"
},
"app.skins.add-button.drag-and-drop": {
"message": "Trascina e rilascia"
"message": "Trascina qui"
},
"app.skins.apply-button": {
"message": "Applica"
@@ -348,7 +444,7 @@
"message": "Vuoi davvero eliminare questa skin?"
},
"app.skins.dropped-file-error.text": {
"message": "Non è stato possibile leggere il file rilasciato."
"message": "Non è stato possibile leggere il file trascinato."
},
"app.skins.dropped-file-error.title": {
"message": "Impossibile processare il file"
@@ -357,13 +453,13 @@
"message": "Modifica"
},
"app.skins.modal.add-skin-button": {
"message": "Salva"
"message": "Aggiungi"
},
"app.skins.modal.add-title": {
"message": "Aggiungi una nuova skin"
"message": "Aggiungi una skin"
},
"app.skins.modal.arm-style-section": {
"message": "Modello del giocatore"
"message": "Modello"
},
"app.skins.modal.arm-style-slim": {
"message": "Snello"
@@ -381,7 +477,7 @@
"message": "Modifica la skin"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "Modifica la skin prima!"
"message": "Modifica prima la skin!"
},
"app.skins.modal.no-cape-tooltip": {
"message": "Nessun mantello"
@@ -390,19 +486,19 @@
"message": "Nessuno"
},
"app.skins.modal.replace-texture-button": {
"message": "Sfoglia"
"message": "Cambia"
},
"app.skins.modal.save-skin-button": {
"message": "Salva"
},
"app.skins.modal.saving-tooltip": {
"message": "Salvataggio..."
"message": "Salvando..."
},
"app.skins.modal.texture-section": {
"message": "File della skin"
"message": "Aspetto"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "Carica una skin prima!"
"message": "Carica prima una skin!"
},
"app.skins.preview.edit-button": {
"message": "Modifica"
@@ -411,11 +507,17 @@
"message": "Anteprima"
},
"app.skins.rate-limit.text": {
"message": "Stai modificando la skin troppo spesso. I server di Mojang hanno limitato le nostre richieste. Aspetta un momento prima di riprovare."
"message": "Stai cambiando skin troppo spesso. I server di Mojang hanno limitato le nostre richieste. Aspetta un momento prima di riprovare."
},
"app.skins.rate-limit.title": {
"message": "Rallenta!"
},
"app.skins.reorder-error.text": {
"message": "L'ordine delle skin non è stato salvato."
},
"app.skins.reorder-error.title": {
"message": "Errore nel riordine delle skin"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -441,7 +543,7 @@
"message": "Mounts of Mayhem"
},
"app.skins.section.saved-skins": {
"message": "Raccolta"
"message": "Skin salvate"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
@@ -459,13 +561,13 @@
"message": "Accedi"
},
"app.skins.sign-in.description": {
"message": "Accedi col tuo account Minecraft per usare la gestione delle skin."
"message": "Accedi col tuo account Minecraft per poter gestire le skin qui."
},
"app.skins.sign-in.rinthbot-alt": {
"message": "Modrinth Bot emozionato"
},
"app.skins.sign-in.title": {
"message": "Accedi"
"message": "Effettua l'accesso"
},
"app.skins.title": {
"message": "Seleziona una skin"
@@ -522,7 +624,7 @@
"message": "Stringi un'amicizia"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, one {richiesta} other {richieste}} d''amicizia"
"message": "{count, plural, one {# richiesta} other {# richieste}} d''amicizia"
},
"friends.add-friend.submit": {
"message": "Invia richiesta d'amicizia"
@@ -689,6 +791,30 @@
"instance.settings.tabs.general.name": {
"message": "Nome"
},
"instance.settings.tabs.general.update-channel": {
"message": "Canale di aggiornamento"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Le versioni stabili, beta e alpha saranno mostrate come aggiornamenti disponibili."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Le versioni stabili e beta saranno mostrate come aggiornamenti disponibili."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Stabile"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Solo le versioni stabili saranno mostrate come aggiornamenti disponibili."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Seleziona il canale di aggiornamento"
},
"instance.settings.tabs.hooks": {
"message": "Hook di avvio"
},
@@ -738,16 +864,16 @@
"message": "Java e memoria"
},
"instance.settings.tabs.java.custom-environment-variables": {
"message": "Variabili d'ambiente"
"message": "Personalizzate"
},
"instance.settings.tabs.java.custom-java-arguments": {
"message": "Argomenti JVM"
"message": "Personalizzati"
},
"instance.settings.tabs.java.custom-java-installation": {
"message": "Eseguibile di Java"
"message": "Personalizzata"
},
"instance.settings.tabs.java.custom-memory-allocation": {
"message": "Memoria allocata"
"message": "Personalizzata"
},
"instance.settings.tabs.java.enter-environment-variables": {
"message": "Inserisci le variabili d'ambiente..."
@@ -812,6 +938,9 @@
"instance.worlds.copy_address": {
"message": "Copia indirizzo"
},
"instance.worlds.create_shortcut": {
"message": "Crea collegamento"
},
"instance.worlds.dont_show_on_home": {
"message": "Non mostrare nella home"
},
+76 -37
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Java {version} をダウンロード中"
},
"app.action-bar.downloading-update": {
"message": "アップデートをダウンロード中"
},
"app.action-bar.downloads": {
"message": "ダウンロード"
},
@@ -42,7 +45,7 @@
"message": "高度なレンダリング"
},
"app.appearance-settings.color-theme.description": {
"message": "Modrinth Appでのお好みのテーマを選択してください"
"message": "Modrinth Appでのお好みのテーマを選択してください"
},
"app.appearance-settings.color-theme.title": {
"message": "テーマ"
@@ -60,25 +63,25 @@
"message": "デフォルトの起動ページ"
},
"app.appearance-settings.hide-nametag.description": {
"message": "スキンページでプレイヤー名の上にある名前タグを無効にします。"
"message": "スキンページでプレイヤー上部のネームタグを非表示にします。"
},
"app.appearance-settings.hide-nametag.title": {
"message": "名札を非表示にする"
"message": "ネームタグを非表示にする"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "ホームページの「ジャンプバック」セクションには、最近追加されたワールドが含まれています。"
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "再び世界へ飛び込もう"
"message": "最近のワールドを表示"
},
"app.appearance-settings.minimize-launcher.description": {
"message": "Minecraftが起動した時、ランチャーを最小化します"
"message": "Minecraftが起動した時、ランチャーを最小化します"
},
"app.appearance-settings.minimize-launcher.title": {
"message": "ランチャーを最小化"
},
"app.appearance-settings.native-decorations.description": {
"message": "OSデフォルトのウィンドウUIを使用します(アプリの再起動が必要です)"
"message": "OSデフォルトのウィンドウUIを使用します。(アプリの再起動が必要です"
},
"app.appearance-settings.native-decorations.title": {
"message": "OSのウィンドウUIを使用"
@@ -86,17 +89,23 @@
"app.appearance-settings.select-option": {
"message": "オプションを選択してください"
},
"app.appearance-settings.show-play-time.description": {
"message": "インスタンスのプレイ時間を表示します。"
},
"app.appearance-settings.show-play-time.title": {
"message": "プレイ時間を表示"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "サイドバーの表示/非表示を切り替える機能を有効にします。"
},
"app.appearance-settings.toggle-sidebar.title": {
"message": "サイドバーの表示/非表示を切り替える"
"message": "サイドバーの表示切替を有効化"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "Modrinth上にホストされていないModrinth Packファイル.mrpack)をインストールしようとした場合、インストール前にそのリスクを理解していただけるよう確認します。"
"message": "Modrinthで配布されていないModrinth Pack.mrpack)をインストールする場合、事前にリスクについて警告します。"
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "未知のMODパックインストールする前に警告してください"
"message": "不明なModパックインストール前に警告する"
},
"app.auth-servers.unreachable.body": {
"message": "Minecraftの認証サーバーは現在停止している可能性があります。インターネット接続を確認し、しばらくしてからもう一度お試しください。"
@@ -131,12 +140,6 @@
"app.browse.server.installing": {
"message": "インストール中"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Modパックをインストール中..."
},
"app.export-modal.description-placeholder": {
"message": "Modパックの説明を入力…"
},
@@ -206,12 +209,6 @@
"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導入済み"
},
@@ -230,21 +227,9 @@
"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": "必須コンテンツ"
},
@@ -254,6 +239,9 @@
"app.modal.install-to-play.install-button": {
"message": "インストール"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {#個のMod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "必須のModパック"
},
@@ -302,8 +290,23 @@
"app.settings.tabs.resource-management": {
"message": "リソース管理"
},
"app.skins.modal.arm-style-slim": {
"message": "スリム"
},
"app.skins.modal.arm-style-wide": {
"message": "ワイド"
},
"app.skins.modal.cape-fallback-name": {
"message": "マント"
},
"app.skins.modal.cape-section": {
"message": "マント"
},
"app.skins.modal.no-cape-tooltip": {
"message": "マントなし"
},
"app.skins.modal.replace-texture-button": {
"message": "置きかえる"
"message": "アップロード"
},
"app.skins.modal.texture-section": {
"message": "テクスチャ"
@@ -311,6 +314,42 @@
"app.skins.preview.edit-button": {
"message": "編集"
},
"app.skins.section.builders-and-biomes": {
"message": "ビルダーズ&バイオーム"
},
"app.skins.section.chase-the-skies": {
"message": "チェイス・ザ・スカイ"
},
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth プライド"
},
"app.skins.section.mounts-of-mayhem": {
"message": "マウント・オブ・メイヘム"
},
"app.skins.section.saved-skins": {
"message": "保存済みのスキン"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "銅の時代"
},
"app.skins.section.the-garden-awakens": {
"message": "ガーデン・アウェイケンズ"
},
"app.skins.section.tiny-takeover": {
"message": "タイニー・テイクオーバー"
},
"app.skins.sign-in.rinthbot-alt": {
"message": "ワクワクしている Modrinth ボット"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} のダウンロードが完了しました。今すぐ更新するには再読み込みするか、Modrinth Appを閉じる際に自動更新されます。"
},
@@ -329,9 +368,6 @@
"app.update-popup.download-complete": {
"message": "ダウンロード完了"
},
"app.update-popup.reload": {
"message": "再読み込み"
},
"app.update-popup.title": {
"message": "アップデートが利用可能です"
},
@@ -362,6 +398,9 @@
"friends.action.add-friend": {
"message": "フレンドを追加"
},
"friends.action.view-friend-requests": {
"message": "{count}件の友達リクエスト"
},
"friends.add-friend.submit": {
"message": "フレンド申請を送信"
},
+6 -33
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Java {version} 다운로드 중"
},
"app.action-bar.downloading-update": {
"message": "업데이트 다운로드중"
},
"app.action-bar.downloads": {
"message": "다운로드"
},
@@ -152,12 +155,6 @@
"app.content-install.no-compatible-versions": {
"message": "{compatibilityLabel} 버전과 일치하는 버전이 없습니다. 설치할 버전을 선택하세요. 종속성은 자동으로 설치되지 않습니다."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "모드팩 설치중..."
},
"app.export-modal.description-placeholder": {
"message": "모드팩 설명 입력..."
},
@@ -167,18 +164,12 @@
"app.export-modal.header": {
"message": "모드팩 내보내기"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\"(을)를 포함할까요?"
},
"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": "버전 구분"
},
@@ -233,12 +224,6 @@
"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": "수정됨"
},
@@ -257,21 +242,9 @@
"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": "콘텐츠 설치 필요"
},
@@ -500,9 +473,6 @@
"app.update-popup.download-complete": {
"message": "다운로드 완료"
},
"app.update-popup.reload": {
"message": "새로고침"
},
"app.update-popup.title": {
"message": "업데이트 가능"
},
@@ -533,6 +503,9 @@
"friends.action.add-friend": {
"message": "친구 추가"
},
"friends.action.view-friend-requests": {
"message": "{count}건의 친구 요청"
},
"friends.add-friend.submit": {
"message": "친구 요청 보내기"
},
+6 -33
View File
@@ -146,12 +146,6 @@
"app.content-install.no-compatible-versions": {
"message": "Tiada versi tersedia yang sepadan dengan {compatibilityLabel}. Pilih versi untuk dipasang juga. Kebergantungan tidak akan dipasang secara automatik."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Sedang memasang pek mod..."
},
"app.export-modal.description-placeholder": {
"message": "Masukkan keterangan pek mod..."
},
@@ -161,18 +155,12 @@
"app.export-modal.header": {
"message": "Eksport pek mod"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Sertakan \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nama Pek Mod"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nama pek mod"
},
"app.export-modal.select-files-label": {
"message": "Konfigurasikan fail yang disertakan dalam eksport ini"
},
"app.export-modal.version-number-label": {
"message": "Nombor versi"
},
@@ -227,12 +215,6 @@
"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"
},
@@ -251,21 +233,9 @@
"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"
},
@@ -275,6 +245,9 @@
"app.modal.install-to-play.install-button": {
"message": "Pasang"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pek mod yang diperlukan"
},
@@ -455,9 +428,6 @@
"app.update-popup.download-complete": {
"message": "Muat turun selesai"
},
"app.update-popup.reload": {
"message": "Muat semula"
},
"app.update-popup.title": {
"message": "Kemas kini tersedia"
},
@@ -488,6 +458,9 @@
"friends.action.add-friend": {
"message": "Tambah rakan"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, other {permintaan}} rakan"
},
"friends.add-friend.submit": {
"message": "Hantar permintaan rakan"
},
+216 -27
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Java {version} aan het downloaden"
},
"app.action-bar.downloading-update": {
"message": "Update aan het downloaden"
},
"app.action-bar.downloads": {
"message": "Downloads"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Primaire instantie"
},
"app.action-bar.reload-to-update": {
"message": "Herlaadt om te updaten"
},
"app.action-bar.show-more-running-instances": {
"message": "Toon meer actieve instanties"
},
"app.action-bar.stop-instance": {
"message": "Stop instantie"
},
"app.action-bar.update": {
"message": "Bijwerken"
},
"app.action-bar.view-active-downloads": {
"message": "Bekijk actieve downloads"
},
@@ -51,7 +60,7 @@
"message": "Wijzig de pagina waarop de launcher opent."
},
"app.appearance-settings.default-landing-page.home": {
"message": "Home"
"message": "Start"
},
"app.appearance-settings.default-landing-page.library": {
"message": "Bibliotheek"
@@ -86,6 +95,12 @@
"app.appearance-settings.select-option": {
"message": "Selecteer een optie"
},
"app.appearance-settings.show-play-time.description": {
"message": "Toont hoeveel tijd je aan een instantie hebt besteed."
},
"app.appearance-settings.show-play-time.title": {
"message": "Speeltijd laten zien"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Schakelt de mogelijkheid om de zijbalk in- of uit te schakelen in."
},
@@ -143,11 +158,8 @@
"app.browse.server.installing": {
"message": "Installeren"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Modpack aan het installeren..."
"app.content-install.no-compatible-versions": {
"message": "Er zijn geen beschikbare versies die overeenkomen met {compatibilityLabel}. Selecteer toch een versie om te installeren. Afhankelijkheden worden niet automatisch geïnstalleerd."
},
"app.export-modal.description-placeholder": {
"message": "Voeg modpack beschrijving in..."
@@ -158,18 +170,12 @@
"app.export-modal.header": {
"message": "Exporteer modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Betrek \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpack Naam"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Modpack naam"
},
"app.export-modal.select-files-label": {
"message": "Configureer welke bestanden bij deze export zijn betrokken"
},
"app.export-modal.version-number-label": {
"message": "Versie nummer"
},
@@ -224,9 +230,6 @@
"app.instance.worlds.browse-servers": {
"message": "Zoek servers"
},
"app.instance.worlds.delete-world-title": {
"message": "Weet je zeker dat je deze wereld definitief wilt verwijderen?"
},
"app.instance.worlds.filter-modded": {
"message": "Gemod"
},
@@ -245,17 +248,8 @@
"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.this-server": {
"message": "deze server"
"app.instance.worlds.search-worlds-placeholder": {
"message": "Zoek werelden"
},
"app.modal.install-to-play.content-required": {
"message": "Content vereist"
@@ -296,6 +290,9 @@
"app.project.install-button.already-installed": {
"message": "Dit project is al geïnstalleerd"
},
"app.project.install-context.back-to-browse": {
"message": "Terug naar Ontdekken"
},
"app.project.install-context.install-content-to-instance": {
"message": "Installeer content naar instantie"
},
@@ -323,6 +320,153 @@
"app.settings.tabs.resource-management": {
"message": "Bronnenbeheer"
},
"app.skins.add-button": {
"message": "Skin toevoegen"
},
"app.skins.add-button.drag-and-drop": {
"message": "Slepen en neerzetten"
},
"app.skins.apply-button": {
"message": "Toepassen"
},
"app.skins.delete-button": {
"message": "Skin verwijderen"
},
"app.skins.delete-modal.description": {
"message": "De geselecteerde skin wordt permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt."
},
"app.skins.delete-modal.title": {
"message": "Weet u zeker dat u deze skin wilt verwijderen?"
},
"app.skins.dropped-file-error.text": {
"message": "Kan het neergezette bestand niet lezen."
},
"app.skins.dropped-file-error.title": {
"message": "Fout bij het verwerken van het bestand"
},
"app.skins.edit-button": {
"message": "Skin aanpassen"
},
"app.skins.modal.add-skin-button": {
"message": "Skin toevoegen"
},
"app.skins.modal.add-title": {
"message": "Voeg een skin toe"
},
"app.skins.modal.arm-style-section": {
"message": "Armstijl"
},
"app.skins.modal.arm-style-slim": {
"message": "Slank"
},
"app.skins.modal.arm-style-wide": {
"message": "Breed"
},
"app.skins.modal.cape-fallback-name": {
"message": "Cape"
},
"app.skins.modal.cape-section": {
"message": "Cape"
},
"app.skins.modal.edit-title": {
"message": "Skin aan het aanpassen"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "Verander eerst iets aan de skin!"
},
"app.skins.modal.no-cape-tooltip": {
"message": "Geen cape"
},
"app.skins.modal.none-cape-option": {
"message": "Geen"
},
"app.skins.modal.replace-texture-button": {
"message": "Texture vervangen"
},
"app.skins.modal.save-skin-button": {
"message": "Skin opslaan"
},
"app.skins.modal.saving-tooltip": {
"message": "Opslaan..."
},
"app.skins.modal.texture-section": {
"message": "Textuur"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "Upload eerst een skin!"
},
"app.skins.preview.edit-button": {
"message": "Skin aanpassen"
},
"app.skins.previewing-badge": {
"message": "Voorvertonen"
},
"app.skins.rate-limit.text": {
"message": "U verandert te snel van skin. Mojang's servers hebben nieuwe aanvragen tijdelijk geblokkeerd. Wacht een moment voordat u het opnieuw probeert."
},
"app.skins.rate-limit.title": {
"message": "Rustig aan!"
},
"app.skins.reorder-error.text": {
"message": "Je skinvolgorde kon niet worden opgeslagen."
},
"app.skins.reorder-error.title": {
"message": "Het herschikken van skins is mislukt."
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
"app.skins.section.default-skins": {
"message": "Standaardskins"
},
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
"app.skins.section.modrinth-pride.tooltip": {
"message": "U heeft deze skins ontvangen omdat u tijdens Pride Month heeft gedoneerd aan een Modrinth Pride-inzamelingsactie."
},
"app.skins.section.mounts-of-mayhem": {
"message": "Mounts of Mayhem"
},
"app.skins.section.saved-skins": {
"message": "Opgeslagen skins"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "The Copper Age"
},
"app.skins.section.the-garden-awakens": {
"message": "The Garden Awakens"
},
"app.skins.section.tiny-takeover": {
"message": "Tiny Takeover"
},
"app.skins.sign-in.button": {
"message": "Inloggen"
},
"app.skins.sign-in.description": {
"message": "Log in op je Minecraft-account om de skinbeheerfuncties van de Modrinth-app te gebruiken."
},
"app.skins.sign-in.rinthbot-alt": {
"message": "Excited Modrinth Bot"
},
"app.skins.sign-in.title": {
"message": "Log in"
},
"app.skins.title": {
"message": "Skinkiezer"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} is klaar met downloaden. Herlaad om nu te updaten, of automatisch wanneer je de Modrinth App afsluit."
},
@@ -342,7 +486,7 @@
"message": "Downloaden voltooid"
},
"app.update-popup.reload": {
"message": "Herlaad"
"message": "Herladen om te updaten"
},
"app.update-popup.title": {
"message": "Update beschikbaar"
@@ -542,6 +686,30 @@
"instance.settings.tabs.general.name": {
"message": "Naam"
},
"instance.settings.tabs.general.update-channel": {
"message": "Updatekanaal"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Release-, beta- en alfaversies worden als beschikbare updates weergegeven."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Release- en beta-versies worden als beschikbare updates weergegeven.\n"
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Release"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Alleen releaseversies worden als beschikbare updates weergegeven."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Selecteer updatekanaal"
},
"instance.settings.tabs.hooks": {
"message": "Opstart haakjes"
},
@@ -590,6 +758,24 @@
"instance.settings.tabs.java": {
"message": "Java en geheugen"
},
"instance.settings.tabs.java.custom-environment-variables": {
"message": "Aangepaste omgevingsvariabelen"
},
"instance.settings.tabs.java.custom-java-arguments": {
"message": "Aangepaste Java-argumenten"
},
"instance.settings.tabs.java.custom-java-installation": {
"message": "Aangepaste Java-installatie"
},
"instance.settings.tabs.java.custom-memory-allocation": {
"message": "Aangepaste geheugentoewijzing"
},
"instance.settings.tabs.java.enter-environment-variables": {
"message": "Voer omgevingsvariabelen in…"
},
"instance.settings.tabs.java.enter-java-arguments": {
"message": "Voer Java-argumenten in…"
},
"instance.settings.tabs.java.environment-variables": {
"message": "Environment variabelen"
},
@@ -605,6 +791,9 @@
"instance.settings.tabs.java.java-memory": {
"message": "Voorgeschreven geheugen"
},
"instance.settings.tabs.java.java-path-placeholder": {
"message": "//pad/naar/java"
},
"instance.settings.tabs.window": {
"message": "Venster"
},
+155 -29
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Pobieranie Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Pobieranie aktualizacji"
},
"app.action-bar.downloads": {
"message": "Pobrania"
},
"app.action-bar.hide-more-running-instances": {
"message": "Ukryj włączone instancje"
},
"app.action-bar.install.dismiss": {
"message": "Odrzuć"
},
"app.action-bar.install.open-instance": {
"message": "Otwórz instancję"
},
"app.action-bar.install.retry": {
"message": "Spróbuj ponownie"
},
"app.action-bar.install.unknown-instance": {
"message": "Nieznana instancja"
},
"app.action-bar.installs": {
"message": "Instalacje"
},
"app.action-bar.make-primary-instance": {
"message": "Ustaw jako główną instancję"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Główna instancja"
},
"app.action-bar.reload-to-update": {
"message": "Załaduj ponownie, by zaktualizować"
},
"app.action-bar.show-more-running-instances": {
"message": "Pokaż więcej włączonych instancji"
},
"app.action-bar.stop-instance": {
"message": "Zatrzymaj instancję"
},
"app.action-bar.update": {
"message": "Zaktualizuj"
},
"app.action-bar.view-active-downloads": {
"message": "Pokaż aktualnie pobierane"
},
@@ -42,7 +66,7 @@
"message": "Zaawansowane renderowanie"
},
"app.appearance-settings.color-theme.description": {
"message": "Wybierz preferowany motyw w Modrinth App."
"message": "Wybierz preferowany motyw w Modrinth app."
},
"app.appearance-settings.color-theme.title": {
"message": "Motyw"
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Pokaż czas gry"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Automatycznie pomija mało znaczące potwierdzenia takie jak duplikaty instalacji paczek modów, usuwanie zwykłej zawartości, masowe aktualizacje, odłączanie paczek modów i naprawianie. Ostrzeżenia o potencjalnie niebezpiecznych akcjach będą nadal pokazywane."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Pomiń mało znaczące ostrzeżenia"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Umożliwia przełączenie paska bocznego."
},
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Nie znaleziono żadnych wersji pasujących do {compatibilityLabel}. Wybierz wersję, by zainstalować ją mimo tego. Zależności nie zostaną zainstalowane automatycznie."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Instalowanie paczki modów..."
},
"app.export-modal.description-placeholder": {
"message": "Wprowadź opis paczki modów..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Eksportuj paczkę modów"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Dołączyć \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nazwa paczki modów"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nazwa paczki modów"
},
"app.export-modal.select-files-label": {
"message": "Skonfiguruje jakie pliki zostaną załączone w tym eksporcie"
},
"app.export-modal.version-number-label": {
"message": "Numer wersji"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Pobieranie zawartości"
},
"app.install.phase.downloading_minecraft": {
"message": "Pobieranie Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Pobieranie pliku paczki"
},
"app.install.phase.extracting_overrides": {
"message": "Wyodrębnianie nadpisań"
},
"app.install.phase.finalizing": {
"message": "Finalizowanie"
},
"app.install.phase.preparing_instance": {
"message": "Przygotowywanie instancji"
},
"app.install.phase.preparing_java": {
"message": "Przygotowywanie środowiska Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Pobieranie Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Wypakowywanie Javy {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Pobieranie Javy {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Przygotowywanie Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Sprawdzanie Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Ustalanie manifestu paczki"
},
"app.install.phase.resolving_loader": {
"message": "Ustalanie programu ładującego"
},
"app.install.phase.resolving_minecraft": {
"message": "Ustalanie wersji Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Ustalanie treści"
},
"app.install.phase.rolling_back": {
"message": "Przywracanie"
},
"app.install.phase.running_loader_processors": {
"message": "Uruchamianie procesorów programu ładującego"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Wszystkie dane z Twojej instancji zostaną trwale usunięte, w tym Twoje światy, pliki konfiguracji i jakakolwiek dodana zawartość."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instalacja"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Pobieranie {current, number}/{total, number} projektów..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Kończenie aktualizacji..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Sprawdzanie wersji..."
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
@@ -230,11 +311,17 @@
"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-modal.delete-button": {
"message": "Usuń świat"
},
"app.instance.worlds.delete-world-title": {
"message": "Czy na pewno chcesz trwale usunąć ten świat?"
"app.instance.worlds.delete-world-modal.title": {
"message": "Usuń świat"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Ten świat zostanie trwale usunięty z tej instancji. Tej akcji nie będzie można cofnąć."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Usuwanie {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Zmodowany"
@@ -254,23 +341,23 @@
"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-modal.remove-button": {
"message": "Usuń serwer"
},
"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-modal.title": {
"message": "Usuń serwer"
},
"app.instance.worlds.remove-server-title": {
"message": "Czy jesteś pewny, że chcesz usunąć {name}?"
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Ten serwer zostanie trwale usunięty z tej instancji. Tej akcji nie będzie można cofnąć."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Usuwanie {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"
"message": "Wymagana zawartość"
},
"app.modal.install-to-play.header": {
"message": "Zainstaluj, aby grać"
@@ -278,6 +365,9 @@
"app.modal.install-to-play.install-button": {
"message": "Zainstaluj"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} few {# mody} other {# modów}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Wymagana paczka modów"
},
@@ -351,7 +441,7 @@
"message": "To trwale usunie wybraną skórkę. Tej akcji nie można odwrócić."
},
"app.skins.delete-modal.title": {
"message": "Czy na pewno chcesz usunąć tą skórkę?"
"message": "Czy na pewno chcesz usunąć tę skórkę?"
},
"app.skins.dropped-file-error.text": {
"message": "Nie udało się odczytać wybranego pliku."
@@ -422,6 +512,12 @@
"app.skins.rate-limit.title": {
"message": "Zwolnij!"
},
"app.skins.reorder-error.text": {
"message": "Nie udało się zapisać kolejności skórek."
},
"app.skins.reorder-error.title": {
"message": "Nie udało się zmienić kolejności skórek"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -495,7 +591,7 @@
"message": "Pobieranie ukończone"
},
"app.update-popup.reload": {
"message": "Załaduj ponownie"
"message": "Załaduj ponownie, by zaktualizować"
},
"app.update-popup.title": {
"message": "Dostępna aktualizacja"
@@ -527,6 +623,9 @@
"friends.action.add-friend": {
"message": "Dodaj znajomego"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, one {zaproszenie} few {zaproszenia} other {zaproszeń}} do znajomych"
},
"friends.add-friend.submit": {
"message": "Wyślij zaproszenie"
},
@@ -692,6 +791,30 @@
"instance.settings.tabs.general.name": {
"message": "Nazwa"
},
"instance.settings.tabs.general.update-channel": {
"message": "Kanał aktualizacji"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Wersje stabilne, beta i alfa będą pokazane jako dostępne aktualizacje."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Tylko wersje stabilne i wersje beta będą pokazane jako dostępne aktualizacje."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Stabilny"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Tylko wersje stabilne będą pokazane jako dostępne aktualizacje."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Wybierz kanał aktualizacji"
},
"instance.settings.tabs.hooks": {
"message": "Haczyki startowe"
},
@@ -815,6 +938,9 @@
"instance.worlds.copy_address": {
"message": "Kopiuj adres"
},
"instance.worlds.create_shortcut": {
"message": "Utwórz skrót"
},
"instance.worlds.dont_show_on_home": {
"message": "Nie pokazuj na stronie głównej"
},
+183 -60
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Baixando Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Baixando atualização"
},
"app.action-bar.downloads": {
"message": "Downloads"
},
"app.action-bar.hide-more-running-instances": {
"message": "Ocultar mais instâncias em execução"
},
"app.action-bar.install.dismiss": {
"message": "Dispensar"
},
"app.action-bar.install.open-instance": {
"message": "Abrir instância"
},
"app.action-bar.install.retry": {
"message": "Tentar novamente"
},
"app.action-bar.install.unknown-instance": {
"message": "Instância desconhecida"
},
"app.action-bar.installs": {
"message": "Instalações"
},
"app.action-bar.make-primary-instance": {
"message": "Tornar instância principal"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Instância principal"
},
"app.action-bar.reload-to-update": {
"message": "Recarregar para atualizar"
},
"app.action-bar.show-more-running-instances": {
"message": "Exibir mais instâncias em execução"
},
"app.action-bar.stop-instance": {
"message": "Parar instância"
},
"app.action-bar.update": {
"message": "Atualizar"
},
"app.action-bar.view-active-downloads": {
"message": "Ver downloads ativos"
},
@@ -42,7 +66,7 @@
"message": "Renderização avançada"
},
"app.appearance-settings.color-theme.description": {
"message": "Selecione seu tema de cores preferido para o Modrinth App."
"message": "Escolha seu tema de cores preferido para o Modrinth App."
},
"app.appearance-settings.color-theme.title": {
"message": "Tema de cores"
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Exibir tempo jogado"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Pula automaticamente as confirmações desnecessárias como duplica de instalação de pacotes de mods, exclusão de conteúdo normal, atualizações em massa, desvinculo de pacotes de mods e diálogos de reparo. Os avisos de perigo ainda serão exibidos."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Pular avisos desnecessários"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Permite alternar a barra lateral."
},
@@ -99,7 +129,7 @@
"message": "Alternar barra lateral"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "Se você tentar instalar um arquivo Modrinth Pack (.mrpack) não hospedado no Modrinth, garantiremos que você entenda os riscos antes de instalá-lo."
"message": "Se você tentar instalar um arquivo Modrinth Pack (.mrpack) não hospedado no Modrinth, garantiremos que você entenda os riscos antes de instalar."
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "Avise-me antes de instalar pacotes de mods desconhecidos"
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Nenhuma versão disponível corresponde a {compatibilityLabel}. Selecione uma versão para instalar mesmo assim. As dependências não serão instaladas automaticamente."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Instalando pacote de mods..."
},
"app.export-modal.description-placeholder": {
"message": "Insira uma descrição para o pacote de mods..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Exportar pacote de mods"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Incluir \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Nome do pacote de mods"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Nome do pacote de mods"
},
"app.export-modal.select-files-label": {
"message": "Configure quais arquivos serão incluídos na exportação"
},
"app.export-modal.version-number-label": {
"message": "Número da versão"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Instalando conteúdo"
},
"app.install.phase.downloading_minecraft": {
"message": "Instalando Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Instalando arquivo de pacote"
},
"app.install.phase.extracting_overrides": {
"message": "Extraindo substitutos"
},
"app.install.phase.finalizing": {
"message": "Finalizando"
},
"app.install.phase.preparing_instance": {
"message": "Preparando instância"
},
"app.install.phase.preparing_java": {
"message": "Preparando Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Baixando Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extraindo Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Obtendo Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Preparando Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validando Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Lendo manifestação de pacote"
},
"app.install.phase.resolving_loader": {
"message": "Resolvendo loader"
},
"app.install.phase.resolving_minecraft": {
"message": "Resolvendo Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Resolvendo conteúdo"
},
"app.install.phase.rolling_back": {
"message": "Indo de volta"
},
"app.install.phase.running_loader_processors": {
"message": "Executando processadores de loader"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos os dados para sua instância serão excluídos permanentemente, incluindo seus mundos, configs e todo o conteúdo instalado."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instância"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Instalando {current, number}/{total, number} projeto(s)..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Finalizando atualização..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Resolvendo versões..."
},
"app.instance.mods.content-type-project": {
"message": "projeto"
},
@@ -233,11 +314,17 @@
"app.instance.worlds.browse-servers": {
"message": "Explorar servidores"
},
"app.instance.worlds.delete-world-description": {
"message": "'{name}' será **excluído permanentemente**, e não poderá ser recuperado."
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Excluir mundo"
},
"app.instance.worlds.delete-world-title": {
"message": "Deseja mesmo excluir este mundo permanentemente?"
"app.instance.worlds.delete-world-modal.title": {
"message": "Excluir mundo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Este mundo será permanentemente excluído desta instância. Esta ação não pode ser desfeita."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Excluindo {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Modificado"
@@ -257,21 +344,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Nenhum servidor ou mundo adicionado"
},
"app.instance.worlds.remove-server-description": {
"message": "'{name}' será removido da lista, inclusive no jogo, e não poderá ser recuperado."
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Remover servidor"
},
"app.instance.worlds.remove-server-description-with-address": {
"message": "'{name}' ({address}) será removido da lista, inclusive no jogo, e não poderá ser recuperado."
"app.instance.worlds.remove-server-modal.title": {
"message": "Remover servidor"
},
"app.instance.worlds.remove-server-title": {
"message": "Deseja mesmo remover {name}?"
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "O servidor será removido da lista de servidores e da lista de servidores no jogo. Você pode re-adicioná-lo depois se souber o endereço."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Removendo {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"
},
@@ -281,6 +368,9 @@
"app.modal.install-to-play.install-button": {
"message": "Instalar"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, =0 {Nenhum mod} one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pacote de mods necessário"
},
@@ -425,6 +515,12 @@
"app.skins.rate-limit.title": {
"message": "Mais lento!"
},
"app.skins.reorder-error.text": {
"message": "Não foi possível salvar a ordem de skins."
},
"app.skins.reorder-error.title": {
"message": "Falhou ao reordenar skins"
},
"app.skins.section.builders-and-biomes": {
"message": "Construtores & Biomas"
},
@@ -456,7 +552,7 @@
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "A Era do Cobre"
"message": "The Copper Age"
},
"app.skins.section.the-garden-awakens": {
"message": "The Garden Awakens"
@@ -468,7 +564,7 @@
"message": "Iniciar sessão"
},
"app.skins.sign-in.description": {
"message": "Faça login na sua conta do Minecraft para usar os recursos de gerenciamento de skins do Modrinth app."
"message": "Inicie sessão na sua conta do Minecraft para usar os recursos de gerenciamento de skins no Modrinth App."
},
"app.skins.sign-in.rinthbot-alt": {
"message": "Modrinth Bot animado"
@@ -480,10 +576,10 @@
"message": "Seletor de skins"
},
"app.update-popup.body.download-complete": {
"message": "O Modrinth App v{version} foi baixado. Recarregue para atualizar agora ou a atualização será aplicada automaticamente ao fechar o Modrinth App."
"message": "O Modrinth App v{version} foi instalado. Recarregue para atualizar agora ou ela será feita automaticamente ao fechar o Modrinth App."
},
"app.update-popup.body.linux": {
"message": "O Modrinth App v{version} está disponível. Use seu gerenciador de pacotes para atualizar e obter os recursos e correções mais recentes!"
"message": "O Modrinth App v{version} está disponível. Use o gerenciador de pacotes para atualizar para obter novos recursos e correções!"
},
"app.update-popup.body.metered": {
"message": "O Modrinth App v{version} já está disponível! Já que sua rede é limitada, não a instalamos automaticamente."
@@ -498,7 +594,7 @@
"message": "Instalação concluída"
},
"app.update-popup.reload": {
"message": "Recarregar"
"message": "Recarregar para atualizar"
},
"app.update-popup.title": {
"message": "Atualização disponível"
@@ -552,13 +648,13 @@
"message": "<link>Adicione amigos</link> para ver o que eles estão jogando!"
},
"friends.friend.cancel-request": {
"message": "Cancelar solicitação"
"message": "Cancelar pedido"
},
"friends.friend.remove-friend": {
"message": "Remover amigo"
},
"friends.friend.request-sent": {
"message": "Solicitação de amizade enviada"
"message": "Pedido de amizade enviado"
},
"friends.friend.view-profile": {
"message": "Ver perfil"
@@ -612,7 +708,7 @@
"message": "Editar servidor"
},
"instance.edit-world.hide-from-home": {
"message": "Esconder da página inicial"
"message": "Ocultar da página inicial"
},
"instance.edit-world.name": {
"message": "Nome"
@@ -654,7 +750,7 @@
"message": "Excluir instância"
},
"instance.settings.tabs.general.delete.description": {
"message": "Exclui permanentemente uma instância do seu dispositivo, incluindo seus mundos, configurações e todo o conteúdo instalado. Tome cuidado, pois, uma vez excluída, a instância não pode ser recuperada."
"message": "Exclui permanentemente uma instância do seu dispositivo, incluindo seus mundos, configs e todo o conteúdo instalado. Tome cuidado, assim que excluído, a instância não pode ser recuperada."
},
"instance.settings.tabs.general.deleting.button": {
"message": "Excluindo..."
@@ -669,7 +765,7 @@
"message": "Duplicar instância"
},
"instance.settings.tabs.general.duplicate-instance.description": {
"message": "Cria uma cópia desta instância, incluindo mundos, configurações, mods, etc."
"message": "Cria uma cópia desta instância, incluindo mundos, configs, mods, etc."
},
"instance.settings.tabs.general.edit-icon": {
"message": "Editar ícone"
@@ -687,7 +783,7 @@
"message": "Grupos"
},
"instance.settings.tabs.general.library-groups.create": {
"message": "Criar novo grupo"
"message": "Criar grupo novo"
},
"instance.settings.tabs.general.library-groups.description": {
"message": "Os grupos da biblioteca permitem que você organize suas instâncias em diferentes seções."
@@ -698,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "Nome"
},
"instance.settings.tabs.general.update-channel": {
"message": "Canal de atualização"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "As versões release, beta e alpha aparecerão como atualizações disponíveis."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "As versões release e beta aparecerão como atualizações disponíveis."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Release"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Apenas versões release aparecerão como atualizações disponíveis."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Selecionar canal de atualização"
},
"instance.settings.tabs.hooks": {
"message": "Ações de inicialização"
},
@@ -705,7 +825,7 @@
"message": "Ações de inicialização personalizadas"
},
"instance.settings.tabs.hooks.description": {
"message": "Essas ações permitem que usuários mais experientes executem comandos do sistema antes e depois de inicializar o jogo."
"message": "As ações permitem usuários experientes que executem certos comandos do sistema antes ou após iniciar o jogo."
},
"instance.settings.tabs.hooks.post-exit": {
"message": "Ao sair"
@@ -714,16 +834,16 @@
"message": "Executado após o jogo fechar."
},
"instance.settings.tabs.hooks.post-exit.enter": {
"message": "Insira o comando a ser executado após o jogo fechar..."
"message": "Insira o comando ao sair..."
},
"instance.settings.tabs.hooks.pre-launch": {
"message": "Pré-inicialização"
"message": "Ao iniciar"
},
"instance.settings.tabs.hooks.pre-launch.description": {
"message": "Executado antes que a instância seja inicializada."
},
"instance.settings.tabs.hooks.pre-launch.enter": {
"message": "Insira o comando a ser executado antes da inicialização..."
"message": "Insira o comando ao iniciar..."
},
"instance.settings.tabs.hooks.title": {
"message": "Ações de inicialização do jogo"
@@ -735,13 +855,13 @@
"message": "Comando auxiliar para iniciar o Minecraft."
},
"instance.settings.tabs.hooks.wrapper.enter": {
"message": "Insira um comando..."
"message": "Insira um comando auxiliar..."
},
"instance.settings.tabs.installation": {
"message": "Instalação"
},
"instance.settings.tabs.installation.loader-version": {
"message": "Versão do {loader}"
"message": "Versão {loader}"
},
"instance.settings.tabs.java": {
"message": "Java e memória"
@@ -786,13 +906,13 @@
"message": "Janela"
},
"instance.settings.tabs.window.custom-window-settings": {
"message": "Configurações de janela personalizada"
"message": "Opções de janela personalizada"
},
"instance.settings.tabs.window.fullscreen": {
"message": "Tela cheia"
},
"instance.settings.tabs.window.fullscreen.description": {
"message": "Faz com que o jogo inicie em tela cheia (usando options.txt)."
"message": "Faz o jogo iniciar em tela cheia (usando options.txt)."
},
"instance.settings.tabs.window.height": {
"message": "Altura"
@@ -821,8 +941,11 @@
"instance.worlds.copy_address": {
"message": "Copiar endereço"
},
"instance.worlds.create_shortcut": {
"message": "Criar atalho"
},
"instance.worlds.dont_show_on_home": {
"message": "Não mostrar no início"
"message": "Não exibir na página inicial"
},
"instance.worlds.game_already_open": {
"message": "A instância já está aberta"
@@ -834,19 +957,19 @@
"message": "Servidor incompatível"
},
"instance.worlds.linked_server": {
"message": "Gerenciado pelo projeto de servidor"
"message": "Gerenciado pelo projeto do servidor"
},
"instance.worlds.no_contact": {
"message": "Não foi possível conectar-se ao servidor"
},
"instance.worlds.no_server_quick_play": {
"message": "Você só pode entrar diretamente em servidores a partir do Minecraft 1.0.5"
"message": "Só é possível entrar diretamente em servidores a partir do Minecraft Alpha 1.0.5"
},
"instance.worlds.no_singleplayer_quick_play": {
"message": "Você só pode entrar diretamente em mundos de um jogador a partir do Minecraft 1.20+"
"message": "Só é possível entrar em mundos um jogador diretamente a partir do Minecraft 1.20"
},
"instance.worlds.play_instance": {
"message": "Jogar na instância"
"message": "Iniciar instância"
},
"instance.worlds.view_instance": {
"message": "Ver instância"
@@ -858,7 +981,7 @@
"message": "Adicionar conta"
},
"minecraft-account.label": {
"message": "Conta do minecraft"
"message": "Conta de Minecraft"
},
"minecraft-account.not-signed-in": {
"message": "Não conectado"
@@ -870,7 +993,7 @@
"message": "Selecionar conta"
},
"minecraft-account.sign-in": {
"message": "Faça login no Minecraft"
"message": "Inicie sessão no Minecraft"
},
"search.filter.locked.instance": {
"message": "Fornecido pela instância"
@@ -879,7 +1002,7 @@
"message": "A versão do jogo é fornecida pela instância"
},
"search.filter.locked.instance-loader.title": {
"message": "O carregador é fornecido pela instância"
"message": "O loader é fornecido pela instância"
},
"search.filter.locked.instance.sync": {
"message": "Sincronizar com a instância"
@@ -888,19 +1011,19 @@
"message": "Fornecido pelo servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Apenas mods do lado do cliente podem ser adicionados à instância do servidor"
"message": "Somente mods do cliente podem ser adicionados à instância do servidor"
},
"search.filter.locked.server-game-version.title": {
"message": "A versão do jogo é fornecida pelo servidor"
},
"search.filter.locked.server-loader.title": {
"message": "O carregador é fornecido pelo servidor"
"message": "O loader é fornecido pelo servidor"
},
"unknown-pack-warning-modal.body": {
"message": "Um arquivo só é revisado se for enviado para o Modrinth, não importa o seu formato de arquivo (incluindo .mrpack)."
"message": "Um arquivo só é revisado se for enviado no Modrinth, independente do formato do arquivo (incluindo .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Não mostrar este aviso novamente"
"message": "Não exibir aviso novamente"
},
"unknown-pack-warning-modal.header": {
"message": "Confirmar instalação"
@@ -909,10 +1032,10 @@
"message": "Instalar mesmo assim"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Malware é frequentemente distribuído através de arquivos de pacotes de mods compartilhados em plataformas como o Discord."
"message": "O malware é distribuído frequentemente através de arquivos de pacote de mods compartilhados em plataformas como Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Não foi possível encontrar este arquivo no Modrinth. Recomendamos fortemente que você instale apenas arquivos de fontes confiáveis."
"message": "Não encontramos este arquivo no Modrinth. Nós recomendamos fortemente instalar arquivos apenas de fontes confiáveis."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Aviso de arquivo desconhecido"
+30 -12
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "A descarregar a versão do java {version}"
},
"app.action-bar.downloading-update": {
"message": "A transferir atualização"
},
"app.action-bar.downloads": {
"message": "Downloads"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Instância principal"
},
"app.action-bar.reload-to-update": {
"message": "Recarregar para atualizar"
},
"app.action-bar.show-more-running-instances": {
"message": "Mostrar mais instâncias a correr"
},
"app.action-bar.stop-instance": {
"message": "Para instância"
},
"app.action-bar.update": {
"message": "Atualizar"
},
"app.action-bar.view-active-downloads": {
"message": "Ver instâncias ativas"
},
@@ -35,6 +44,9 @@
"app.action-bar.view-logs": {
"message": "Ver logs"
},
"app.appearance-settings.advanced-rendering.description": {
"message": "Ativa a renderização avançada, como efeitos de desfoque, que podem causar problemas de desempenho sem o uso de renderização acelerada por hardware."
},
"app.appearance-settings.advanced-rendering.title": {
"message": "Renderização avançada"
},
@@ -56,6 +68,12 @@
"app.appearance-settings.default-landing-page.title": {
"message": "Página de carregamento principal"
},
"app.appearance-settings.minimize-launcher.title": {
"message": "Minimizar launcher"
},
"app.appearance-settings.select-option": {
"message": "Seleciona uma opção"
},
"app.auth-servers.unreachable.body": {
"message": "Os servidores de autenticação do Minecraft poderão estar em baixo de momento. Verifica a tua ligação à internet e tenta novamente mais tarde."
},
@@ -80,6 +98,9 @@
"app.browse.discover-servers": {
"message": "Descobrir servidores"
},
"app.browse.project-type.modpacks": {
"message": "Modpacks"
},
"app.export-modal.description-placeholder": {
"message": "Insere a descrição do modpack..."
},
@@ -149,9 +170,6 @@
"app.instance.worlds.browse-servers": {
"message": "Procurar servidores"
},
"app.instance.worlds.delete-world-title": {
"message": "Tens a certeza que queres apagar permanentemente este mundo?"
},
"app.instance.worlds.filter-modded": {
"message": "Com Mods"
},
@@ -170,15 +188,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Sem servidores ou mundos adicionados"
},
"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"
},
@@ -239,6 +251,15 @@
"app.settings.tabs.resource-management": {
"message": "Gestão de recursos"
},
"app.skins.delete-modal.title": {
"message": "Tens a certeza que queres apagar esta skin?"
},
"app.skins.dropped-file-error.text": {
"message": "Não foi possível ler o ficheiro largado."
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} acabou de ser transferida. Recarrega para atualizar agora, ou automaticamente quando fechares a Modrinth App."
},
@@ -257,9 +278,6 @@
"app.update-popup.download-complete": {
"message": "Transferência concluída"
},
"app.update-popup.reload": {
"message": "Recarregar"
},
"app.update-popup.title": {
"message": "Atualização disponível"
},
@@ -83,9 +83,6 @@
"app.update-popup.download-complete": {
"message": "Descărcare finalizată"
},
"app.update-popup.reload": {
"message": "Reîncărcați"
},
"app.update-popup.title": {
"message": "Actualizare disponibilă"
},
+150 -18
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Скачивание Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Скачивание"
},
"app.action-bar.downloads": {
"message": "Загрузки"
},
"app.action-bar.hide-more-running-instances": {
"message": "Скрыть другие активные сборки"
},
"app.action-bar.install.dismiss": {
"message": "Закрыть"
},
"app.action-bar.install.open-instance": {
"message": "Открыть сборку"
},
"app.action-bar.install.retry": {
"message": "Повторить"
},
"app.action-bar.install.unknown-instance": {
"message": "Неизвестная сборка"
},
"app.action-bar.installs": {
"message": "Установки"
},
"app.action-bar.make-primary-instance": {
"message": "Сделать основной сборкой"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Основная сборка"
},
"app.action-bar.reload-to-update": {
"message": "Перезапустить"
},
"app.action-bar.show-more-running-instances": {
"message": "Показать другие активные сборки"
},
"app.action-bar.stop-instance": {
"message": "Остановить сборку"
},
"app.action-bar.update": {
"message": "Обновить"
},
"app.action-bar.view-active-downloads": {
"message": "Посмотреть текущие загрузки"
},
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Время игры"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Автоматически пропускает подтверждения с низким уровнем риска, такие как установка дубликатов сборок, обычное удаление контента, массовые обновления, отключение сборок и запросы на восстановление. Опасные предупреждения по-прежнему будут отображаться."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Пропустить несущественные предупреждения"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Показывать кнопку скрытия боковой панели."
},
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Не найдено доступных версий, совместимых с {compatibilityLabel}. Выберите версию для установки вручную. Зависимости не будут установлены автоматически."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Установка сборки..."
},
"app.export-modal.description-placeholder": {
"message": "Введите описание сборки..."
},
@@ -167,24 +191,69 @@
"app.export-modal.header": {
"message": "Экспорт сборки"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Добавить «{file}» в экспорт?"
},
"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.install.phase.downloading_content": {
"message": "Скачивание контента"
},
"app.install.phase.downloading_minecraft": {
"message": "Скачивание Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Скачивание файла сборки"
},
"app.install.phase.extracting_overrides": {
"message": "Извлечение переопределений"
},
"app.install.phase.finalizing": {
"message": "Завершение"
},
"app.install.phase.preparing_instance": {
"message": "Подготовка сборки"
},
"app.install.phase.preparing_java": {
"message": "Подготовка Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Скачивание Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Распаковка Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Получение Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Подготовка Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Проверка Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Чтение манифеста сборки"
},
"app.install.phase.resolving_minecraft": {
"message": "Загрузка версий"
},
"app.install.phase.resolving_pack": {
"message": "Загрузка контента"
},
"app.install.phase.rolling_back": {
"message": "Откат"
},
"app.install.phase.running_loader_processors": {
"message": "Запуск процессоров загрузчика"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Все данные сборки будут удалены навсегда, в том числе миры, настройки и весь установленный контент."
},
@@ -209,6 +278,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Сборка"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Скачивание {current, number}/{total, number} проектов..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Завершение обновления..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Загрузка версий..."
},
"app.instance.mods.content-type-project": {
"message": "проект"
},
@@ -230,8 +308,17 @@
"app.instance.worlds.browse-servers": {
"message": "Найти серверы"
},
"app.instance.worlds.delete-world-title": {
"message": "Вы действительно хотите удалить этот мир?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Удалить"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Удаление мира"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Этот мир будет удалён навсегда из этой сборки. Отменить это действие нельзя."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Удаление {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Модовые"
@@ -251,11 +338,17 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Нет серверов и миров"
},
"app.instance.worlds.remove-server-title": {
"message": "Вы действительно хотите удалить {name}?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Удалить"
},
"app.instance.worlds.this-server": {
"message": "этот сервер"
"app.instance.worlds.remove-server-modal.title": {
"message": "Удаление сервера"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Этот сервер будет удалён из списка серверов, в том числе и в игре. Его можно вернуть, зная адрес."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Удаление {name}"
},
"app.modal.install-to-play.content-required": {
"message": "Требуется дополнительный контент"
@@ -266,6 +359,9 @@
"app.modal.install-to-play.install-button": {
"message": "Установить"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# мод} few {# мода} other {# модов}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Необходимая сборка"
},
@@ -410,6 +506,12 @@
"app.skins.rate-limit.title": {
"message": "Не так быстро!"
},
"app.skins.reorder-error.text": {
"message": "Ваш порядок скинов не удалось сохранить."
},
"app.skins.reorder-error.title": {
"message": "Не удалось изменить порядок скинов"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -515,6 +617,9 @@
"friends.action.add-friend": {
"message": "Добавить в друзья"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, one {запрос} few {запроса} other {запросов}} дружбы"
},
"friends.add-friend.submit": {
"message": "Отправить запрос дружбы"
},
@@ -677,6 +782,30 @@
"instance.settings.tabs.general.name": {
"message": "Название"
},
"instance.settings.tabs.general.update-channel": {
"message": "Канал обновления"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Альфа"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Релизные, бета и альфа версии будут отображаться как доступные обновления."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Бета"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Релизные и бета версии будут отображаться как доступные обновления."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Релиз"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Только релизные версии будут отображаться как доступные обновления."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Выберите канал обновления"
},
"instance.settings.tabs.hooks": {
"message": "Команды запуска"
},
@@ -800,6 +929,9 @@
"instance.worlds.copy_address": {
"message": "Копировать адрес"
},
"instance.worlds.create_shortcut": {
"message": "Создать ярлык"
},
"instance.worlds.dont_show_on_home": {
"message": "Не показывать на главной"
},
+4 -19
View File
@@ -92,12 +92,6 @@
"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"
},
@@ -116,21 +110,9 @@
"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"
},
@@ -174,11 +156,14 @@
"message": "Minecraft svet"
},
"instance.edit-world.title": {
"message": "Izmeni svet"
"message": "Promeni svet"
},
"instance.server-modal.placeholder-name": {
"message": "Minecraft Server"
},
"instance.settings.tabs.general": {
"message": "Generalno"
},
"instance.settings.tabs.general.deleting.button": {
"message": "Brisanje..."
},
+301 -28
View File
@@ -2,17 +2,35 @@
"app.action-bar.downloading-java": {
"message": "Laddar ner Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Laddar ner uppdatering"
},
"app.action-bar.downloads": {
"message": "Nedladdningar"
},
"app.action-bar.hide-more-running-instances": {
"message": "Göm fler pågående instanser"
"message": "Göm fler aktiva instanser"
},
"app.action-bar.install.dismiss": {
"message": "Avvisa"
},
"app.action-bar.install.open-instance": {
"message": "Öppna instans"
},
"app.action-bar.install.retry": {
"message": "Försök igen"
},
"app.action-bar.install.unknown-instance": {
"message": "Okänd instans"
},
"app.action-bar.installs": {
"message": "Installationer"
},
"app.action-bar.make-primary-instance": {
"message": "Gör till primärinstans"
},
"app.action-bar.no-instances-running": {
"message": "Ingen pågående instans"
"message": "Ingen aktiva instans"
},
"app.action-bar.offline": {
"message": "Offline"
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Primärinstans"
},
"app.action-bar.reload-to-update": {
"message": "Uppdatera (ladda om)"
},
"app.action-bar.show-more-running-instances": {
"message": "Visa fler pågående instanser"
"message": "Visa fler aktiva instanser"
},
"app.action-bar.stop-instance": {
"message": "Avsluta instans"
},
"app.action-bar.update": {
"message": "Uppdatera"
},
"app.action-bar.view-active-downloads": {
"message": "Visa aktiva nedladdningar"
},
@@ -65,6 +89,15 @@
"app.appearance-settings.hide-nametag.title": {
"message": "Dölj namnskylt"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "Inkluderar de senaste världarna i \"Hoppa tillbaka i världar\" på hemsidan."
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "Hoppa tillbaka i världar"
},
"app.appearance-settings.minimize-launcher.description": {
"message": "Minimerar launchern när Minecraft startas."
},
"app.appearance-settings.minimize-launcher.title": {
"message": "Minimera programstartare"
},
@@ -77,18 +110,39 @@
"app.appearance-settings.select-option": {
"message": "Välj ett alternativ"
},
"app.appearance-settings.show-play-time.description": {
"message": "Visar hur mycket tid du spelat på en instans."
},
"app.appearance-settings.show-play-time.title": {
"message": "Visa speltid"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Hoppar över bekräftelser med låg risk, såsom installation av dubbla modpaket, radering av vanligt innehåll, massuppdateringar, avlänkning av modpaket och reparationsmeddelanden. Varningar om farliga händelser visas fortfarande."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Hoppa över icke nödvändiga varningar"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Låter dig växla sidofältet."
},
"app.appearance-settings.toggle-sidebar.title": {
"message": "Växla sidofält"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "Om du försöker installera en Modrinth Pack fil (.mrpack) som inte kommer från Modrinth ser vi till att du känner till riskerna innan du installerar den."
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "Varna mig innan jag installerar okända modpaket"
},
"app.auth-servers.unreachable.body": {
"message": "Minecrafts autentiseringsservrar kan vara nere just nu. Kontrollera din internetanslutning och försök igen senare."
},
"app.auth-servers.unreachable.header": {
"message": "Kan ej nå autentiseringsservrarna"
},
"app.browse.add-servers-to-instance": {
"message": "Lägger till server till instans"
},
"app.browse.add-to-an-instance": {
"message": "Lägg till i en instans"
},
@@ -119,14 +173,14 @@
"app.browse.project-type.modpacks": {
"message": "Modpaket"
},
"app.browse.server-instance-content-warning": {
"message": "Att lägga till innehåll kan bryta kompatibiliteten när du ansluter dig till servern. Allt tillagt innehåll kommer att förloras när du uppdaterar serverinstansen."
},
"app.browse.server.installing": {
"message": "Installerar"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Installerar modpaket..."
"app.content-install.no-compatible-versions": {
"message": "Ingen tillgänglig version matchar {compatibilityLabel}. Välj en version att installera ändå. Beroenden kommer inte installeras automatiskt."
},
"app.export-modal.description-placeholder": {
"message": "Ange modpaketets beskrivning..."
@@ -137,9 +191,6 @@
"app.export-modal.header": {
"message": "Exportera modpaket"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Inkludera \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Modpaketets namn"
},
@@ -152,8 +203,56 @@
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "Laddar ner innehåll"
},
"app.install.phase.downloading_minecraft": {
"message": "Laddar ner Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "Laddar ner pack fil"
},
"app.install.phase.extracting_overrides": {
"message": "Extraherar överskrivningar"
},
"app.install.phase.finalizing": {
"message": "Avslutar"
},
"app.install.phase.preparing_instance": {
"message": "Förbereder instans"
},
"app.install.phase.preparing_java": {
"message": "Förbereder Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Laddar ner Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Extraherar Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Hämtar Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Förbereder Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Bekräftar Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "Läser pack information"
},
"app.install.phase.resolving_loader": {
"message": "Laddar ner loadern"
},
"app.install.phase.resolving_minecraft": {
"message": "Laddar ner Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "Laddar ner innehåll"
},
"app.instance.confirm-delete.admonition-body": {
"message": "All data från din instans kommer att raderas permanent, inklusive dina världar, konfigurationer och allt installerat innehåll."
"message": "All data från din instans kommer permanent raderas, däribland dina världar, konfigurationer samt allt installerat innehåll."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Detta kan inte ogöras"
@@ -176,6 +275,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Instans"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "Laddar ner {current, number}/{total, number} projekt..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Slutför uppdatering..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Laddar versioner..."
},
"app.instance.mods.content-type-project": {
"message": "projekt"
},
@@ -200,11 +308,14 @@
"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-modal.delete-button": {
"message": "Ta bort värld"
},
"app.instance.worlds.delete-world-title": {
"message": "Är du säker på att du vill permanent radera denna värld?"
"app.instance.worlds.delete-world-modal.title": {
"message": "Ta bort värld"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Raderar {name}"
},
"app.instance.worlds.filter-modded": {
"message": "Moddad"
@@ -224,21 +335,18 @@
"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-modal.remove-button": {
"message": "Ta bort server"
},
"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-modal.title": {
"message": "Ta bort server"
},
"app.instance.worlds.remove-server-title": {
"message": "Är du säker på att du vill radera {name}?"
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Raderar {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"
},
@@ -275,6 +383,15 @@
"app.modal.update-to-play.update-required-description": {
"message": "En uppdatering krävs för att spela {name}. Vänligen uppdatera till senaste version för att starta spelet."
},
"app.project.install-button.already-installed": {
"message": "Detta projekt är redan installerat"
},
"app.project.install-context.back-to-browse": {
"message": "Tillbaka till upptäck"
},
"app.project.install-context.install-content-to-instance": {
"message": "Installera innehåll till instans"
},
"app.settings.developer-mode-enabled": {
"message": "Utvecklarläge aktiverat."
},
@@ -302,9 +419,27 @@
"app.skins.add-button": {
"message": "Lägg till utseende"
},
"app.skins.add-button.drag-and-drop": {
"message": "Dra och släpp"
},
"app.skins.apply-button": {
"message": "Tillämpa"
},
"app.skins.delete-button": {
"message": "Ta bort utseende"
},
"app.skins.delete-modal.description": {
"message": "Du kommer permanent radera det valda utseendet. Detta kan inte ångras."
},
"app.skins.delete-modal.title": {
"message": "Är du säker på att du vill radera utseendet?"
},
"app.skins.dropped-file-error.text": {
"message": "Misslyckades att läsa den släppta filen."
},
"app.skins.dropped-file-error.title": {
"message": "Fel med behandlingen av filen"
},
"app.skins.edit-button": {
"message": "Redigera utseende"
},
@@ -353,6 +488,78 @@
"app.skins.modal.texture-section": {
"message": "Textur"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "Ladda upp utseende först!"
},
"app.skins.preview.edit-button": {
"message": "Redigera utseende"
},
"app.skins.previewing-badge": {
"message": "Förhandsvisa"
},
"app.skins.rate-limit.text": {
"message": "Du ändrar ditt utseende för ofta. Mojangs servrar har temporärt blockerat ytterligare förfrågningar. Vänligen vänta en stund innan du försöker igen."
},
"app.skins.rate-limit.title": {
"message": "Sakta ner!"
},
"app.skins.reorder-error.text": {
"message": "Ditt utseende kunde inte sparas."
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
"app.skins.section.default-skins": {
"message": "Standard utseenden"
},
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
"app.skins.section.modrinth-pride.tooltip": {
"message": "Du har fått dessa utseende eftersom du donerat till en Modrinth Pride insamling under pride månaden."
},
"app.skins.section.mounts-of-mayhem": {
"message": "Mounts of Mayhem"
},
"app.skins.section.saved-skins": {
"message": "Sparade utseenden"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "The Copper Age"
},
"app.skins.section.the-garden-awakens": {
"message": "The Garden Awakens"
},
"app.skins.section.tiny-takeover": {
"message": "Tiny Takeover"
},
"app.skins.sign-in.button": {
"message": "Logga in"
},
"app.skins.sign-in.description": {
"message": "Snälla logga in din Minecraft konto att använda hud hantering förmågor i Modrinth appen."
},
"app.skins.sign-in.rinthbot-alt": {
"message": "Exalterad Modrinth bot"
},
"app.skins.sign-in.title": {
"message": "Vänligen logga in"
},
"app.skins.title": {
"message": "Utseende väljare"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} har laddats ner. Ladda om för att uppdatera nu, eller automatiskt när du stänger Modrinth App."
},
@@ -372,7 +579,7 @@
"message": "Nedladdning slutförd"
},
"app.update-popup.reload": {
"message": "Ladda om"
"message": "Ladda om för att uppdatera"
},
"app.update-popup.title": {
"message": "Uppdatering tillgänglig"
@@ -572,6 +779,30 @@
"instance.settings.tabs.general.name": {
"message": "Namn"
},
"instance.settings.tabs.general.update-channel": {
"message": "Uppdateringskanal"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Utgåvor, beta och alfa versioner visas som tillgängliga uppdateringar."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Utgåvor och beta versioner visas som tillgängliga uppdateringar."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Utgåva"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Endast utgivna versioner visas som tillgängliga uppdateringar."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Välj uppdateringskanal"
},
"instance.settings.tabs.hooks": {
"message": "Startkrokar"
},
@@ -620,6 +851,24 @@
"instance.settings.tabs.java": {
"message": "Java och minne"
},
"instance.settings.tabs.java.custom-environment-variables": {
"message": "Anpassade miljövariabler"
},
"instance.settings.tabs.java.custom-java-arguments": {
"message": "Anpassade Java-argument"
},
"instance.settings.tabs.java.custom-java-installation": {
"message": "Anpassad Java-installation"
},
"instance.settings.tabs.java.custom-memory-allocation": {
"message": "Anpassad minnestilldelning"
},
"instance.settings.tabs.java.enter-environment-variables": {
"message": "Skriv in miljövariabler..."
},
"instance.settings.tabs.java.enter-java-arguments": {
"message": "Skriv in Java-argument..."
},
"instance.settings.tabs.java.environment-variables": {
"message": "Miljövariabler"
},
@@ -635,6 +884,9 @@
"instance.settings.tabs.java.java-memory": {
"message": "Tilldelat minne"
},
"instance.settings.tabs.java.java-path-placeholder": {
"message": "/sökväg/till/java"
},
"instance.settings.tabs.window": {
"message": "Fönster"
},
@@ -651,7 +903,7 @@
"message": "Höjd"
},
"instance.settings.tabs.window.height.description": {
"message": "Höjden på spel-fönstret vid uppstart."
"message": "Höjden på spelfönstret vid uppstart."
},
"instance.settings.tabs.window.height.enter": {
"message": "Ange höjd..."
@@ -660,7 +912,7 @@
"message": "Bredd"
},
"instance.settings.tabs.window.width.description": {
"message": "Bredden på spel-fönstret vid uppstart."
"message": "Bredden på spelfönstret vid uppstart."
},
"instance.settings.tabs.window.width.enter": {
"message": "Ange bredd..."
@@ -674,6 +926,9 @@
"instance.worlds.copy_address": {
"message": "Kopiera adress"
},
"instance.worlds.create_shortcut": {
"message": "Skapa genväg"
},
"instance.worlds.dont_show_on_home": {
"message": "Visa inte på Startsidan"
},
@@ -749,7 +1004,25 @@
"search.filter.locked.server-loader.title": {
"message": "Loader tillhandahålls av servern"
},
"unknown-pack-warning-modal.body": {
"message": "En fil granskas bara om den laddas upp till Modrinth, oavsett dess filformat (däribland .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Visa inte denna varning igen"
},
"unknown-pack-warning-modal.header": {
"message": "Bekräfta installation"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Installera ändå"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Skadeprogram distribueras ofta via modpaketfiler genom att dela dem på plattformar som Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Vi kunde inte hitta filen på Modrinth. Vi rekommenderar starkt att endast installera filer från källor du litar på."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Okänd filvarning"
}
}
+204 -48
View File
@@ -1,36 +1,45 @@
{
"app.action-bar.downloading-java": {
"message": "ดาวน์โหลดเวอร์ชัน Java {version}"
"message": "กำลังดาวน์โหลด Java {version}"
},
"app.action-bar.downloading-update": {
"message": "กำลังดาวน์โหลดอัปเดต"
},
"app.action-bar.downloads": {
"message": "ดาวน์โหลด"
},
"app.action-bar.hide-more-running-instances": {
"message": "ซ่อนการดำเนินการโปรแกรมเบื้องหลัง"
"message": "ซ่อนอินสแตนซ์ที่ทำงานอยู่เพิ่มเติม"
},
"app.action-bar.make-primary-instance": {
"message": "กำหนดโปรแกรมหลัก"
"message": "กำหนดอินสแตนซ์หลัก"
},
"app.action-bar.no-instances-running": {
"message": "ไม่มีโปรแกรมใดดำเนินการ"
"message": "ไม่มีอินสแตนซ์ที่กำลังทำงาน"
},
"app.action-bar.offline": {
"message": "ออฟไลน์"
},
"app.action-bar.primary-instance": {
"message": "โปรแกรมหลัก"
"message": "อินสแตนซ์หลัก"
},
"app.action-bar.reload-to-update": {
"message": "รีโหลดเพื่ออัปเดต"
},
"app.action-bar.show-more-running-instances": {
"message": "แสดงการดำเนินการโปรแกรมเบื้องหลังเพิ่มเติม"
"message": "แสดงอินสแตนซ์ที่ทำงานอยู่เพิ่มเติม"
},
"app.action-bar.stop-instance": {
"message": "หยุดการทำงานโปรแกรม"
"message": "หยุดอินสแตนซ์"
},
"app.action-bar.update": {
"message": "อัปเดต"
},
"app.action-bar.view-active-downloads": {
"message": "ดูยอดการดาวน์โหลดที่ยังใช้งาน"
"message": "ดูการดาวน์โหลดที่กำลังดำเนินการอยู่"
},
"app.action-bar.view-instance": {
"message": "ดูโปรแกรม"
"message": "ดูอินสแตนซ์"
},
"app.action-bar.view-logs": {
"message": "ดู Log"
@@ -42,7 +51,7 @@
"message": "การเรนเดอร์ขั้นสูง"
},
"app.appearance-settings.color-theme.description": {
"message": "เลือกธีมสีที่คุณชื่นชอบสำหรับ Modrinth บนอุปกรณ์นี้"
"message": "เลือกธีมสีที่คุณต้องการสำหรับ Modrinth App"
},
"app.appearance-settings.color-theme.title": {
"message": "ธีมสี"
@@ -54,22 +63,22 @@
"message": "หน้าหลัก"
},
"app.appearance-settings.default-landing-page.library": {
"message": "รายการ"
"message": "ไลบรารี"
},
"app.appearance-settings.default-landing-page.title": {
"message": "หน้าเริ่มต้น"
},
"app.appearance-settings.hide-nametag.description": {
"message": "ปิดป้ายชื่อบนศีรษะของผู้เล่นในหน้าแสดงสกิน"
"message": "ซ่อนป้ายชื่อบนศีรษะของผู้เล่นในหน้าแสดงสกิน"
},
"app.appearance-settings.hide-nametag.title": {
"message": "ซ่อนป้ายชื่อ"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "การแสดงผลโลกล่าสุดที่ผู้เล่นล่ในส่วน \"กระโดดกลับเข้าไป\" นหน้าหลัก"
"message": "แสดงโลกที่เล่นล่าสุดในส่วน \"เล่นต่อ\" นหน้าหลัก"
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "กระโดดกลับเข้าไปในโลก"
"message": "เล่นต่อ"
},
"app.appearance-settings.minimize-launcher.description": {
"message": "ย่อลันเชอร์เมื่อกระบวนการการรัน Minecraft เริ่มต้นแล้ว"
@@ -78,13 +87,19 @@
"message": "ย่อลันเชอร์"
},
"app.appearance-settings.native-decorations.description": {
"message": "ใช้ระบบกรอบหน้าต่าง (ต้องการการรีสตาร์ทโปรแกรมใหม่)"
"message": "ใช้กรอบหน้าต่างระบบ (ต้องรีสตาร์ทแอป)"
},
"app.appearance-settings.native-decorations.title": {
"message": "ของตกแต่งดั้งเดิม"
},
"app.appearance-settings.select-option": {
"message": "เลือกตัวเลือก"
"message": "เลือกตัวเลือก"
},
"app.appearance-settings.show-play-time.description": {
"message": "แสดงเวลาที่คุณใช้เล่นในแต่ละอินสแตนซ์"
},
"app.appearance-settings.show-play-time.title": {
"message": "แสดงระยะเวลาที่เล่น"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "เปิดใช้งานตัวเลือกในการเลือกย่อหรือขยายแถบด้านข้าง"
@@ -104,6 +119,12 @@
"app.auth-servers.unreachable.header": {
"message": "ไม่สามารถเชื่อมต่อถึงเซิร์ฟเวอร์ได้"
},
"app.browse.add-servers-to-instance": {
"message": "เพิ่มเซิร์ฟเวอร์ลงในอินสแตนซ์"
},
"app.browse.add-to-an-instance": {
"message": "เพิ่มลงในอินสแตนซ์"
},
"app.browse.add-to-instance": {
"message": "เพิ่มลงในโปรแกรม"
},
@@ -116,6 +137,9 @@
"app.browse.already-added": {
"message": "เพิ่มแล้ว"
},
"app.browse.back-to-instance": {
"message": "กลับสู่อินสแตนซ์"
},
"app.browse.discover-content": {
"message": "สำรวจเนื้อหา"
},
@@ -128,14 +152,14 @@
"app.browse.project-type.modpacks": {
"message": "แพ็กม็อด"
},
"app.browse.server-instance-content-warning": {
"message": "การเพิ่มเนื้อหาอาจทำให้ใช้งานร่วมกันไม่ได้เมื่อเข้าร่วมเซิร์ฟเวอร์ เนื้อหาที่เพิ่มเข้ามาจะหายไปเมื่อคุณอัปเดตอินสแตนซ์ของเซิร์ฟเวอร์"
},
"app.browse.server.installing": {
"message": "กำลังติดตั้ง"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "กำลังติดตั้งแพ็กม็อด..."
"app.content-install.no-compatible-versions": {
"message": "ไม่มีเวอร์ชันที่เข้ากันได้ {compatibilityLabel} โปรดเลือกเวอร์ชันที่จะติดตั้ง ไฟล์ที่จำเป็นจะไม่ถูกติดตั้งโดยอัตโนมัติ"
},
"app.export-modal.description-placeholder": {
"message": "กรอกคำอธิบายมอดแพ็ก.."
@@ -146,18 +170,12 @@
"app.export-modal.header": {
"message": "ส่งออกแพ็กม็อด"
},
"app.export-modal.include-file-accessibility-label": {
"message": "รวมถึง \"{file}\" ใช่หรือไม่"
},
"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": "หมายเลขเวอร์ชัน"
},
@@ -212,12 +230,6 @@
"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": "ม็อด"
},
@@ -236,21 +248,9 @@
"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": "เนื้อหาที่จำเป็น"
},
@@ -260,6 +260,9 @@
"app.modal.install-to-play.install-button": {
"message": "ติดตั้ง"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# ม็อด}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "มอดแพ็กที่จำเป็น"
},
@@ -284,6 +287,15 @@
"app.modal.update-to-play.update-required-description": {
"message": "จำเป็นต้องอัปเดตเพื่อเล่น {name} กรุณาอัปเดตเป็นเวอร์ชันล่าสุดเพื่อเปิดเกม"
},
"app.project.install-button.already-installed": {
"message": "โปรเจคนี้ถูกติดตั้งแล้ว"
},
"app.project.install-context.back-to-browse": {
"message": "กลับไปที่หน้าค้นหา"
},
"app.project.install-context.install-content-to-instance": {
"message": "ติดตั้งเนื้อหาลงสู่อินสแตนซ์"
},
"app.settings.developer-mode-enabled": {
"message": "โหมดนักพัฒนาเปิดอยู่"
},
@@ -308,6 +320,150 @@
"app.settings.tabs.resource-management": {
"message": "การจัดการทรัพยากร"
},
"app.skins.add-button": {
"message": "เพิ่มสกิน"
},
"app.skins.add-button.drag-and-drop": {
"message": "ลากและวาง"
},
"app.skins.apply-button": {
"message": "นำไปใช้"
},
"app.skins.delete-button": {
"message": "ลบสกิน"
},
"app.skins.delete-modal.description": {
"message": "การดำเนินการนี้จะลบสกินที่เลือกอย่างถาวร การกระทำนี้ไม่สามารถย้อนกลับได้"
},
"app.skins.delete-modal.title": {
"message": "คุณแน่ใจหรือว่าต้องการที่จะลบสกินนี้?"
},
"app.skins.dropped-file-error.text": {
"message": "ไม่สามารถอ่านไฟล์ที่ถูกลากและวางได้"
},
"app.skins.dropped-file-error.title": {
"message": "ข้อผิดพลาดในการประมวลผลไฟล์"
},
"app.skins.edit-button": {
"message": "แก้ไขสกิน"
},
"app.skins.modal.add-skin-button": {
"message": "เพิ่มสกิน"
},
"app.skins.modal.add-title": {
"message": "การเพิ่มสกิน"
},
"app.skins.modal.arm-style-section": {
"message": "สไตล์แขน"
},
"app.skins.modal.arm-style-slim": {
"message": "เพรียว"
},
"app.skins.modal.arm-style-wide": {
"message": "กว้าง"
},
"app.skins.modal.cape-fallback-name": {
"message": "ผ้าคลุม"
},
"app.skins.modal.cape-section": {
"message": "ผ้าคลุม"
},
"app.skins.modal.edit-title": {
"message": "แก้ไขสกิน"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "แก้ไขสกินก่อน!"
},
"app.skins.modal.no-cape-tooltip": {
"message": "ไม่มีผ้าคลุม"
},
"app.skins.modal.none-cape-option": {
"message": "ไม่มี"
},
"app.skins.modal.replace-texture-button": {
"message": "เปลี่ยนเท็กซ์เจอร์"
},
"app.skins.modal.save-skin-button": {
"message": "บันทึกสกิน"
},
"app.skins.modal.saving-tooltip": {
"message": "กำลังบันทึก"
},
"app.skins.modal.texture-section": {
"message": "เท็กซ์เจอร์"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "อัพโหลดสกินก่อน!"
},
"app.skins.preview.edit-button": {
"message": "แก้ไขสกิน"
},
"app.skins.previewing-badge": {
"message": "การแสดงตัวอย่าง"
},
"app.skins.rate-limit.text": {
"message": "คุณได้เปลี่ยนสกินบ่อยเกินไป เซิร์ฟเวอร์ของ Mojang ได้บล็อกคำขอเพิ่มเติมชั่วคราว โปรดรอสักครู่ก่อนลองอีกครั้ง"
},
"app.skins.rate-limit.title": {
"message": "ลดความถี่ลง!"
},
"app.skins.reorder-error.text": {
"message": "ไม่สามารถบันทึกลำดับสกินของคุณได้"
},
"app.skins.reorder-error.title": {
"message": "ไม่สามารถจัดเรียงสกินได้"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
"app.skins.section.chase-the-skies": {
"message": "Chase the Skies"
},
"app.skins.section.default-skins": {
"message": "สกินเริ่มต้น"
},
"app.skins.section.minecon-earth-2017": {
"message": "MINECON Earth 2017"
},
"app.skins.section.modrinth": {
"message": "Modrinth"
},
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
"app.skins.section.modrinth-pride.tooltip": {
"message": "คุณได้รับสกินเหล่านี้จากการร่วมบริจาคให้กับกิจกรรมระดมทุน Modrinth Pride ในช่วงเดือน Pride Month"
},
"app.skins.section.mounts-of-mayhem": {
"message": "Mounts of Mayhem"
},
"app.skins.section.saved-skins": {
"message": "สกินที่บันทึกไว้"
},
"app.skins.section.striding-hero": {
"message": "Striding Hero"
},
"app.skins.section.the-copper-age": {
"message": "The Copper Age"
},
"app.skins.section.the-garden-awakens": {
"message": "The Garden Awakens"
},
"app.skins.section.tiny-takeover": {
"message": "Tiny Takeover"
},
"app.skins.sign-in.button": {
"message": "ลงชื่อเข้าใช้"
},
"app.skins.sign-in.description": {
"message": "โปรดลงชื่อเข้าใช้บัญชี Minecraft ของคุณเพื่อใช้งานฟีเจอร์จัดการสกินของแอป Modrinth"
},
"app.skins.sign-in.rinthbot-alt": {
"message": "บอท Modrinth ที่กำลังตื่นเต้น"
},
"app.skins.sign-in.title": {
"message": "โปรดลงชื่อเข้าใช้"
},
"app.update-popup.body.download-complete": {
"message": "ดาวน์โหลด Modrinth v{version} สำเร็จแล้ว เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
},
@@ -326,9 +482,6 @@
"app.update-popup.download-complete": {
"message": "การดาวน์โหลดเสร็จสมบูรณ์"
},
"app.update-popup.reload": {
"message": "โหลดใหม่"
},
"app.update-popup.title": {
"message": "มีการอัปเดตใหม่"
},
@@ -359,6 +512,9 @@
"friends.action.add-friend": {
"message": "เพิ่มเพื่อน"
},
"friends.action.view-friend-requests": {
"message": "คำขอเป็นเพื่อน {count} {count, plural, other {คน}}"
},
"friends.add-friend.submit": {
"message": "ส่งคำขอเป็นเพื่อน"
},
+151 -28
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "Java {version} indiriliyor"
},
"app.action-bar.downloading-update": {
"message": "Güncelleme indiriliyor"
},
"app.action-bar.downloads": {
"message": "İndirmeler"
},
"app.action-bar.hide-more-running-instances": {
"message": "Diğer çalışan kurulumları gizle"
},
"app.action-bar.install.dismiss": {
"message": "Reddet"
},
"app.action-bar.install.open-instance": {
"message": "Kurulumu aç"
},
"app.action-bar.install.retry": {
"message": "Yeniden Dene"
},
"app.action-bar.install.unknown-instance": {
"message": "Bilinmeyen kurulum"
},
"app.action-bar.installs": {
"message": "Yüklenenler"
},
"app.action-bar.make-primary-instance": {
"message": "Birincil kurulum yap"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "Birincil kurulum"
},
"app.action-bar.reload-to-update": {
"message": "Güncellemek için yeniden yükle"
},
"app.action-bar.show-more-running-instances": {
"message": "Daha fazla çalışan kurulum göster"
},
"app.action-bar.stop-instance": {
"message": "Kurulumu durdur"
},
"app.action-bar.update": {
"message": "Güncelleme"
},
"app.action-bar.view-active-downloads": {
"message": "Şu anda indirilienleri göster"
},
@@ -45,7 +69,7 @@
"message": "Modrinth App için tercih ettiğiniz temayı seçin."
},
"app.appearance-settings.color-theme.title": {
"message": "Tema"
"message": "Tema Rengi"
},
"app.appearance-settings.default-landing-page.description": {
"message": "Launcher açıldığında varsayılan olarak açılacak sayfayı seçin."
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "Oynama Süresini Göster"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Mükerrer mod paketi kurulumları, normal içerik silme, toplu güncellemeler, mod paketi bağlantısını kesme ve onarım uyarıları gibi düşük riskli onayları otomatik olarak atlar. Tehlikeli uyarılar gösterilmeye devam edecektir."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Gerekli olmayan uyarıları atla"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "Kenar çubuğunu ayarlama özelliğini aktifleştirir."
},
@@ -135,7 +165,7 @@
"message": "İçerik keşfet"
},
"app.browse.discover-servers": {
"message": "Sunucu keşfet"
"message": "Sunucuları keşfet"
},
"app.browse.hide-added-servers": {
"message": "Zaten eklenmiş sunucuları gizle"
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "Kullanılabilir sürümler {compatibilityLabel} ile eşleşmiyor. Yine de kurulacak bir sürüm seçin. Bağımlılıklar otomatik olarak yüklenmeyecektir."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Mod paketi indiriliyor..."
},
"app.export-modal.description-placeholder": {
"message": "Mod paketi açıklaması girin..."
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "Mod paketini dışa aktar"
},
"app.export-modal.include-file-accessibility-label": {
"message": "\"{file}\" dahil mi?"
},
"app.export-modal.modpack-name-label": {
"message": "Mod Paketi Adı"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Mod paketi adı"
},
"app.export-modal.select-files-label": {
"message": "Bu dışa aktarmaya hangi dosyaların dahil edileceğini yapılandırma"
},
"app.export-modal.version-number-label": {
"message": "Sürüm numarası"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.downloading_content": {
"message": "İçerik indiriliyor"
},
"app.install.phase.downloading_minecraft": {
"message": "Minecraft yükleniyor"
},
"app.install.phase.downloading_pack_file": {
"message": "Paket dosyaları yükleniyor"
},
"app.install.phase.extracting_overrides": {
"message": "Geçersiz kılmalar ayıklanıyor"
},
"app.install.phase.finalizing": {
"message": "Tamamlanıyor"
},
"app.install.phase.preparing_instance": {
"message": "Kurulum hazırlanıyor"
},
"app.install.phase.preparing_java": {
"message": "Java Hazırlanıyor"
},
"app.install.phase.preparing_java.downloading": {
"message": "Java {version} İndiriliyor"
},
"app.install.phase.preparing_java.extracting": {
"message": "Java {version} ayıklanıyor"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Java {version} getiriliyor"
},
"app.install.phase.preparing_java.resolving": {
"message": "Java {version} Hazırlanıyor"
},
"app.install.phase.preparing_java.validating": {
"message": "Java {version} doğrulanıyor"
},
"app.install.phase.reading_pack_manifest": {
"message": "Paket manifesti okunuyor"
},
"app.install.phase.resolving_loader": {
"message": "Yükleyici çözümleniyor"
},
"app.install.phase.resolving_minecraft": {
"message": "Minecraft çözümleniyor"
},
"app.install.phase.resolving_pack": {
"message": "İçerik çözümleniyor"
},
"app.install.phase.rolling_back": {
"message": "Değişiklikler geri alınıyor"
},
"app.install.phase.running_loader_processors": {
"message": "Yükleyici işlemcileri çalıştırılıyor"
},
"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."
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "Kurulum"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} proje indiriliyor..."
},
"app.instance.mods.bulk-update.finishing": {
"message": "Güncelleme tamamlanıyor..."
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "Sürümler çözümleniyor..."
},
"app.instance.mods.content-type-project": {
"message": "proje"
},
@@ -233,11 +314,17 @@
"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-modal.delete-button": {
"message": "Dünya Sil"
},
"app.instance.worlds.delete-world-title": {
"message": "Bu dünyayı kalıcı olarak silmek istediğinizden emin misiniz?"
"app.instance.worlds.delete-world-modal.title": {
"message": "Dünyayı sil"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Bu dünya, bu profilden kalıcı olarak silinecek. Bu işlem geri alınamaz."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "{name} Siliniyor"
},
"app.instance.worlds.filter-modded": {
"message": "Modlu"
@@ -257,21 +344,21 @@
"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-modal.remove-button": {
"message": "Sunucuyu kaldı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-modal.title": {
"message": "Sunucuyu kaldır"
},
"app.instance.worlds.remove-server-title": {
"message": "{name}'i kaldırmak istediğinizden emin misiniz?"
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "Bu sunucu, sunucu listenizden ve oyun içi sunucu listesinden kaldırılacaktır. Adresini biliyorsanız daha sonra tekrar ekleyebilirsiniz."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "{name} kaldırılıyor"
},
"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"
},
@@ -428,6 +515,12 @@
"app.skins.rate-limit.title": {
"message": "Yavaşla!"
},
"app.skins.reorder-error.text": {
"message": "Skinini kaydedemedik."
},
"app.skins.reorder-error.title": {
"message": "Skinler sıralanamadı"
},
"app.skins.section.builders-and-biomes": {
"message": "İnşaatçılar & Biyomlar"
},
@@ -501,7 +594,7 @@
"message": "İndirme tamamlandı"
},
"app.update-popup.reload": {
"message": "Yeniden başlat"
"message": "Güncellemek için yeniden yükle"
},
"app.update-popup.title": {
"message": "Güncelleme mevcut"
@@ -533,6 +626,9 @@
"friends.action.add-friend": {
"message": "Bir arkadaş ekle"
},
"friends.action.view-friend-requests": {
"message": "{count} arkadaşlık isteği"
},
"friends.add-friend.submit": {
"message": "Arkadaşlık isteği gönder"
},
@@ -698,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "Ad"
},
"instance.settings.tabs.general.update-channel": {
"message": "Güncelleme kanalı"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alfa"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Release, beta ve alpha versiyonları mevcut güncellemeler olarak gösterilecek."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Release ve beta versiyonları mevcut güncellemeler olarak gösterilecek."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Yayın"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Yalnızca release versiyonları mevcut güncellemeler olarak gösterilecek."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Güncelleme kanalını seç"
},
"instance.settings.tabs.hooks": {
"message": "Başlatma kancaları"
},
@@ -821,6 +941,9 @@
"instance.worlds.copy_address": {
"message": "Adresi kopyala"
},
"instance.worlds.create_shortcut": {
"message": "Kısayol yap"
},
"instance.worlds.dont_show_on_home": {
"message": "Ana sayfada gösterme"
},
+63 -36
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Завантаження Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Завантаження оновлення"
},
"app.action-bar.downloads": {
"message": "Завантаження"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Основний профіль"
},
"app.action-bar.reload-to-update": {
"message": "Перезавантаження для оновлення"
},
"app.action-bar.show-more-running-instances": {
"message": "Показати більше запущених профілів"
},
"app.action-bar.stop-instance": {
"message": "Зупинити профіль"
},
"app.action-bar.update": {
"message": "Оновлення"
},
"app.action-bar.view-active-downloads": {
"message": "Переглянути активні завантаження"
},
@@ -149,11 +158,8 @@
"app.browse.server.installing": {
"message": "Установлення"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Установлення збірки…"
"app.content-install.no-compatible-versions": {
"message": "Жодна доступна версія не сумісна з {compatibilityLabel}. Виберіть версію, щоб усе одно її встановити. Залежності не встановлюватимуться автоматично."
},
"app.export-modal.description-placeholder": {
"message": "Уведіть опис збірки…"
@@ -164,18 +170,12 @@
"app.export-modal.header": {
"message": "Експортувати збірку"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Уключити «{file}»?"
},
"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": "Номер версії"
},
@@ -195,7 +195,7 @@
"message": "Видалити профіль"
},
"app.instance.modpack-already-installed.body": {
"message": "Ця збірка вже встановлена в профілі <bold>«{instanceName}»</bold>. Ви впевнені, що хочете її скопіювати?"
"message": "Ця збірка вже встановлена в профілі <bold>«{instanceName}»</bold>. Дійсно бажаєте її клонувати?"
},
"app.instance.modpack-already-installed.create": {
"message": "Створити"
@@ -230,9 +230,6 @@
"app.instance.worlds.browse-servers": {
"message": "Переглянути сервери"
},
"app.instance.worlds.delete-world-title": {
"message": "Ви впевнені, що хочете назавжди видалити цей світ?"
},
"app.instance.worlds.filter-modded": {
"message": "Модифіковано"
},
@@ -251,15 +248,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Сервера або світи не додано"
},
"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": "Потрібний уміст"
},
@@ -269,6 +260,9 @@
"app.modal.install-to-play.install-button": {
"message": "Установити"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# мод} few {# мода} other {# модів}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Потрібна збірка"
},
@@ -285,13 +279,13 @@
"message": "Дивитися вміст"
},
"app.modal.update-to-play.header": {
"message": "Оновлення перед грою"
"message": "Оновлення для гри"
},
"app.modal.update-to-play.update-required": {
"message": "Необхідне оновлення"
},
"app.modal.update-to-play.update-required-description": {
"message": "«{name}» потребує оновлення, щоб грати. Будь ласка, оновіть гру до останньої версії, щоб запустити її."
"message": "«{name}» потребує оновлення, щоб грати. Будь ласка, оновіть до останньої версії, щоб запустити гру."
},
"app.project.install-button.already-installed": {
"message": "Проєкт уже встановлено"
@@ -330,7 +324,7 @@
"message": "Додати скін"
},
"app.skins.add-button.drag-and-drop": {
"message": "Перетягніть і вставте"
"message": "Перетягніть сюди"
},
"app.skins.apply-button": {
"message": "Застосувати"
@@ -342,7 +336,7 @@
"message": "Це видалить вибраний скін назавжди. Ця дія не може бути скасована."
},
"app.skins.delete-modal.title": {
"message": "Чи ви впевнені що хочете видалити цей скін?"
"message": "Ви дійсно бажаєте видалити цей скін?"
},
"app.skins.dropped-file-error.text": {
"message": "Неможливо відкрити вибраний файл."
@@ -378,19 +372,19 @@
"message": "Редагування скіна"
},
"app.skins.modal.make-edit-first-tooltip": {
"message": "Відредагуйте скін для початку!"
"message": "Спершу відредагуйте скін!"
},
"app.skins.modal.no-cape-tooltip": {
"message": "Без плаща"
},
"app.skins.modal.none-cape-option": {
"message": "Без"
"message": "Немає"
},
"app.skins.modal.replace-texture-button": {
"message": "Замінити текстуру"
},
"app.skins.modal.save-skin-button": {
"message": "Зберегти вигляд"
"message": "Зберегти скін"
},
"app.skins.modal.saving-tooltip": {
"message": "Збереження…"
@@ -399,7 +393,7 @@
"message": "Текстура"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "Завантажте скін для початку!"
"message": "Спершу завантажте скін!"
},
"app.skins.preview.edit-button": {
"message": "Редагувати скін"
@@ -408,10 +402,16 @@
"message": "Попередній перегляд"
},
"app.skins.rate-limit.text": {
"message": "Ви змінюєте свій скін дуже часто. Сервери Mojang тимчасово заблокували подальші запити. Зачекайте трохи перш ніж спробувати знову."
"message": "Ви змінюєте скін занадто часто. Сервери Mojang тимчасово заблокували подальші запити. Зачекайте трохи, перш ніж спробувати знову."
},
"app.skins.rate-limit.title": {
"message": "Шановний!"
"message": "Повільніше!"
},
"app.skins.reorder-error.text": {
"message": "Ваше замовлення скінів не вдалося зберегти."
},
"app.skins.reorder-error.title": {
"message": "Не вдалося змінити порядок скінів"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
@@ -468,13 +468,13 @@
"message": "Вибір скіну"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} завантажено. Перезапустіть зараз, щоб оновити його, або це відбудеться автоматично після закриття Modrinth App."
"message": "Modrinth App v{version} завантажено. Перезапустіть, щоб оновити зараз, або це відбудеться автоматично після закриття застосунку."
},
"app.update-popup.body.linux": {
"message": "Modrinth App v{version} вже доступно. Скористайтеся вашим менеджером пакетів, щоб отримати найновіші функції та виправлення!"
"message": "Modrinth App v{version} вже доступно. Скористайтеся менеджером пакетів, щоб отримати найновіші функції та виправлення!"
},
"app.update-popup.body.metered": {
"message": "Modrinth App v{version} доступний для завантаження! Оскільки ви на лімітному з’єднанні, ми не завантажили його автоматично."
"message": "Modrinth App v{version} вже доступно! Оскільки ви використовуєте лімітне підключення, ми не завантажили його автоматично."
},
"app.update-popup.changelog": {
"message": "Журнал змін"
@@ -486,7 +486,7 @@
"message": "Завантаження завершено"
},
"app.update-popup.reload": {
"message": "Перезавантажити"
"message": "Перезавантаження для оновлення"
},
"app.update-popup.title": {
"message": "Доступне оновлення"
@@ -510,7 +510,7 @@
"message": "Ще не зіграно"
},
"app.world.world-item.offline": {
"message": "Поза мережею"
"message": "Не в мережі"
},
"app.world.world-item.players-online": {
"message": "{count} у мережі"
@@ -518,6 +518,9 @@
"friends.action.add-friend": {
"message": "Додати друга"
},
"friends.action.view-friend-requests": {
"message": "{count} запит{count, plural, one { у} few {и в} many {ів у} other {у в}} друзі"
},
"friends.add-friend.submit": {
"message": "Надіслати запит у друзі"
},
@@ -683,6 +686,30 @@
"instance.settings.tabs.general.name": {
"message": "Назва"
},
"instance.settings.tabs.general.update-channel": {
"message": "Випуск оновлення"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Альфа"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Випуски, бета- та альфа-версії будуть показуватися як доступні оновлення."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Бета"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Випуски та бета-версії будуть показуватися як доступні оновлення."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Випуск"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Тільки випуски версії будуть показуватися як доступні оновлення."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Оберіть випуск оновлення"
},
"instance.settings.tabs.hooks": {
"message": "Гуки запуску"
},
+48 -30
View File
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Đang tải xuống Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Đang tải xuống bản cập nhật"
},
"app.action-bar.downloads": {
"message": "Tải xuống"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Bản instance chính"
},
"app.action-bar.reload-to-update": {
"message": "Tải lại để cập nhật"
},
"app.action-bar.show-more-running-instances": {
"message": "Hiện thêm các phiên bản đang chạy"
},
"app.action-bar.stop-instance": {
"message": "Dừng instance"
},
"app.action-bar.update": {
"message": "Cập nhật"
},
"app.action-bar.view-active-downloads": {
"message": "Xem các lượt tải xuống"
},
@@ -149,11 +158,8 @@
"app.browse.server.installing": {
"message": "Đang cài đặt"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Đang cài đặt modpack..."
"app.content-install.no-compatible-versions": {
"message": "Không có phiên bản nào phù hợp với {compatibilityLabel}. Nếu vẫn chọn một phiên bản để cài đặt, các gói phụ thuộc sẽ không được tải tự động."
},
"app.export-modal.description-placeholder": {
"message": "Thêm miêu tả cho gói modpack..."
@@ -164,18 +170,12 @@
"app.export-modal.header": {
"message": "Xuất modpack"
},
"app.export-modal.include-file-accessibility-label": {
"message": "Bao gồm \"{file}\"?"
},
"app.export-modal.modpack-name-label": {
"message": "Tên modpack"
},
"app.export-modal.modpack-name-placeholder": {
"message": "Tên modpack"
},
"app.export-modal.select-files-label": {
"message": "Cấu hình các tệp nào được bao gồm trong quá trình xuất phiên bản này"
},
"app.export-modal.version-number-label": {
"message": "Phiên bản"
},
@@ -230,12 +230,6 @@
"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"
},
@@ -254,21 +248,9 @@
"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"
},
@@ -278,6 +260,9 @@
"app.modal.install-to-play.install-button": {
"message": "Tải xuống"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Yêu cầu modpack"
},
@@ -422,6 +407,12 @@
"app.skins.rate-limit.title": {
"message": "Bình tĩnh lại!"
},
"app.skins.reorder-error.text": {
"message": "Không thể lưu thứ tự skin của bạn."
},
"app.skins.reorder-error.title": {
"message": "Sắp xếp lại skin thất bại"
},
"app.skins.section.builders-and-biomes": {
"message": "Builders & Biomes"
},
@@ -495,7 +486,7 @@
"message": "Cài đặt thành công"
},
"app.update-popup.reload": {
"message": "Khởi động lại"
"message": "Tải lại để cập nhật"
},
"app.update-popup.title": {
"message": "Cập nhật mới hiện đang khả dụng"
@@ -527,6 +518,9 @@
"friends.action.add-friend": {
"message": "Thêm một người bạn"
},
"friends.action.view-friend-requests": {
"message": "{count} {count, plural, other {yêu cầu kết bạn}}"
},
"friends.add-friend.submit": {
"message": "Gửi yêu cầu kết bạn"
},
@@ -692,6 +686,30 @@
"instance.settings.tabs.general.name": {
"message": "Tên"
},
"instance.settings.tabs.general.update-channel": {
"message": "Kênh cập nhật"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Sơ khai"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "Các phiên bản chính thức, thử nghiệm và sơ khai sẽ hiển thị trong danh sách cập nhật có sẵn."
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Thử nghiệm"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "Các phiên bản chính thức và thử nghiệm sẽ hiển thị trong danh sách cập nhật có sẵn."
},
"instance.settings.tabs.general.update-channel.release": {
"message": "Chính thức"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "Chỉ phiên bản chính thức sẽ hiển thị trong danh sách cập nhật có sẵn."
},
"instance.settings.tabs.general.update-channel.select": {
"message": "Chọn kênh cập nhật"
},
"instance.settings.tabs.hooks": {
"message": "Khởi chạy Hooks"
},
+166 -31
View File
@@ -2,12 +2,30 @@
"app.action-bar.downloading-java": {
"message": "正在下载 Java {version}"
},
"app.action-bar.downloading-update": {
"message": "下载更新中"
},
"app.action-bar.downloads": {
"message": "下载"
},
"app.action-bar.hide-more-running-instances": {
"message": "隐藏更多运行中的实例"
},
"app.action-bar.install.dismiss": {
"message": "忽略"
},
"app.action-bar.install.open-instance": {
"message": "打开实例"
},
"app.action-bar.install.retry": {
"message": "重试"
},
"app.action-bar.install.unknown-instance": {
"message": "未知的实例"
},
"app.action-bar.installs": {
"message": "安装"
},
"app.action-bar.make-primary-instance": {
"message": "设为主实例"
},
@@ -20,12 +38,18 @@
"app.action-bar.primary-instance": {
"message": "主实例"
},
"app.action-bar.reload-to-update": {
"message": "重新加载以更新"
},
"app.action-bar.show-more-running-instances": {
"message": "显示更多运行中的实例"
},
"app.action-bar.stop-instance": {
"message": "停止实例"
},
"app.action-bar.update": {
"message": "更新"
},
"app.action-bar.view-active-downloads": {
"message": "查看正在下载内容"
},
@@ -63,7 +87,7 @@
"message": "在皮肤页面中禁用你玩家头顶上的名牌。"
},
"app.appearance-settings.hide-nametag.title": {
"message": "隐藏名称标签"
"message": "隐藏名"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "在主页的“快速回到”部分包含最近的世界。"
@@ -92,6 +116,12 @@
"app.appearance-settings.show-play-time.title": {
"message": "显示总游玩时间"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "自动跳过低风险确认,包括重复安装整合包、常规内容删除、批量更新、解除整合包关联及修复提示。危险警告仍会显示。"
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "跳过非必要的警告"
},
"app.appearance-settings.toggle-sidebar.description": {
"message": "启用切换侧边栏的功能。"
},
@@ -99,7 +129,7 @@
"message": "切换侧边栏"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "如果你尝试安装一个未托管在 Modrinth 上的 Modrinth 包文件(.mrpack),我们会确保你在安装之前了解相关风险。"
"message": "如果你尝试安装一个未托管在 Modrinth 上的 Modrinth 整合包文件(.mrpack),我们会确保你在安装之前了解相关风险。"
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "安装未知整合包前提醒我"
@@ -111,7 +141,7 @@
"message": "无法连接到身份验证服务器"
},
"app.browse.add-servers-to-instance": {
"message": "服务器添加到实例"
"message": "添加服务器到实例"
},
"app.browse.add-to-an-instance": {
"message": "添加到实例"
@@ -144,7 +174,7 @@
"message": "整合包"
},
"app.browse.server-instance-content-warning": {
"message": "加入服务器时,添加内容可能会破坏兼容性。当更新服务器实例内容时任何添加的内容也将丢失。"
"message": "加入服务器时,添加内容可能会破坏兼容性。当更新服务器实例内容时任何添加的内容也将丢失。"
},
"app.browse.server.installing": {
"message": "安装中"
@@ -152,12 +182,6 @@
"app.content-install.no-compatible-versions": {
"message": "没有与 {compatibilityLabel} 匹配的可用版本。如果仍要安装,请选择一个版本。依赖项将不会自动安装。"
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "正在安装整合包……"
},
"app.export-modal.description-placeholder": {
"message": "输入整合包描述……"
},
@@ -167,24 +191,72 @@
"app.export-modal.header": {
"message": "导出整合包"
},
"app.export-modal.include-file-accessibility-label": {
"message": "包含 \"{file}\""
},
"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.install.phase.downloading_content": {
"message": "正在下载内容"
},
"app.install.phase.downloading_minecraft": {
"message": "正在下载 Minecraft"
},
"app.install.phase.downloading_pack_file": {
"message": "正在下载包文件"
},
"app.install.phase.extracting_overrides": {
"message": "提取覆盖项"
},
"app.install.phase.finalizing": {
"message": "完成"
},
"app.install.phase.preparing_instance": {
"message": "准备实例"
},
"app.install.phase.preparing_java": {
"message": "正在准备 Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "正在下载 Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "正在提取 Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "正在获取 Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "正在准备 Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "正在校验 Java {version}"
},
"app.install.phase.reading_pack_manifest": {
"message": "正在读取整合包清单"
},
"app.install.phase.resolving_loader": {
"message": "正在解析加载器"
},
"app.install.phase.resolving_minecraft": {
"message": "正在解析 Minecraft"
},
"app.install.phase.resolving_pack": {
"message": "正在解析内容"
},
"app.install.phase.rolling_back": {
"message": "回滚"
},
"app.install.phase.running_loader_processors": {
"message": "正在运行加载处理器"
},
"app.instance.confirm-delete.admonition-body": {
"message": "你实例的所有数据将被永久删除,包括你的世界、配置和所有已安装的内容。"
},
@@ -209,6 +281,15 @@
"app.instance.modpack-already-installed.instance": {
"message": "实例"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "正在下载 {current, number}/{total, number}个 项目……"
},
"app.instance.mods.bulk-update.finishing": {
"message": "正在结束更新……"
},
"app.instance.mods.bulk-update.resolving-versions": {
"message": "正在解析版本……"
},
"app.instance.mods.content-type-project": {
"message": "项目"
},
@@ -233,8 +314,17 @@
"app.instance.worlds.browse-servers": {
"message": "浏览服务器"
},
"app.instance.worlds.delete-world-title": {
"message": "你确定要永久删除这个世界吗?"
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "删除世界"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "删除世界"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "这个世界将会被永久删除,此操作无法撤销。"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "正在删除 {name}"
},
"app.instance.worlds.filter-modded": {
"message": "模组适配"
@@ -254,15 +344,21 @@
"app.instance.worlds.no-worlds-heading": {
"message": "未添加服务器或世界"
},
"app.instance.worlds.remove-server-title": {
"message": "你确定要删除 {name} 吗?"
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "删除服务器"
},
"app.instance.worlds.remove-server-modal.title": {
"message": "删除服务器"
},
"app.instance.worlds.remove-server-modal.warning-body": {
"message": "该服务器将会被移出你的服务器列表,也包括游戏内的服务器列表。之后如果你记得地址,仍可将其添加回来。"
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "正在删除 {name}"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜索 {count} 个世界……"
},
"app.instance.worlds.this-server": {
"message": "此服务器"
},
"app.modal.install-to-play.content-required": {
"message": "需求内容"
},
@@ -272,6 +368,9 @@
"app.modal.install-to-play.install-button": {
"message": "安装"
},
"app.modal.install-to-play.mod-count": {
"message": "{count} 个模组"
},
"app.modal.install-to-play.required-modpack": {
"message": "需求整合包"
},
@@ -416,6 +515,12 @@
"app.skins.rate-limit.title": {
"message": "慢点!"
},
"app.skins.reorder-error.text": {
"message": "未能保存皮肤顺序。"
},
"app.skins.reorder-error.title": {
"message": "皮肤排序失败"
},
"app.skins.section.builders-and-biomes": {
"message": "建造者与生物群系"
},
@@ -489,7 +594,7 @@
"message": "下载完成"
},
"app.update-popup.reload": {
"message": "重新启动"
"message": "重新加载以更新"
},
"app.update-popup.title": {
"message": "有可用的更新"
@@ -521,6 +626,9 @@
"friends.action.add-friend": {
"message": "添加好友"
},
"friends.action.view-friend-requests": {
"message": "{count} 个好友请求"
},
"friends.add-friend.submit": {
"message": "发送好友请求"
},
@@ -645,7 +753,7 @@
"message": "将从设备中永久删除该实例,包括所有存档、配置文件及已安装内容。此操作不可撤销,请谨慎操作。"
},
"instance.settings.tabs.general.deleting.button": {
"message": "删除中..."
"message": "删除中……"
},
"instance.settings.tabs.general.duplicate-button": {
"message": "复制"
@@ -686,6 +794,30 @@
"instance.settings.tabs.general.name": {
"message": "名称"
},
"instance.settings.tabs.general.update-channel": {
"message": "更新渠道"
},
"instance.settings.tabs.general.update-channel.alpha": {
"message": "Alpha 测试版"
},
"instance.settings.tabs.general.update-channel.alpha.description": {
"message": "正式版,beta 和 alpha 测试版会被显示为可用更新。"
},
"instance.settings.tabs.general.update-channel.beta": {
"message": "Beta 测试版"
},
"instance.settings.tabs.general.update-channel.beta.description": {
"message": "正式版和 beta 测试版会被显示为可用更新。"
},
"instance.settings.tabs.general.update-channel.release": {
"message": "正式版"
},
"instance.settings.tabs.general.update-channel.release.description": {
"message": "只有正式版会被显示为可用更新。"
},
"instance.settings.tabs.general.update-channel.select": {
"message": "选择更新渠道"
},
"instance.settings.tabs.hooks": {
"message": "启动 Hooks"
},
@@ -702,7 +834,7 @@
"message": "在游戏关闭后运行。"
},
"instance.settings.tabs.hooks.post-exit.enter": {
"message": "输入退出后运行的命令…"
"message": "输入退出后运行的命令…"
},
"instance.settings.tabs.hooks.pre-launch": {
"message": "启动前"
@@ -711,7 +843,7 @@
"message": "在实例启动前运行。"
},
"instance.settings.tabs.hooks.pre-launch.enter": {
"message": "输入启动前命令..."
"message": "输入启动前命令……"
},
"instance.settings.tabs.hooks.title": {
"message": "游戏启动钩子"
@@ -723,7 +855,7 @@
"message": "用于启动 Minecraft 的包装器命令。"
},
"instance.settings.tabs.hooks.wrapper.enter": {
"message": "输入封装命令..."
"message": "输入封装命令……"
},
"instance.settings.tabs.installation": {
"message": "安装"
@@ -789,7 +921,7 @@
"message": "游戏启动时的窗口高度。"
},
"instance.settings.tabs.window.height.enter": {
"message": "输入高度…"
"message": "输入高度…"
},
"instance.settings.tabs.window.width": {
"message": "宽度"
@@ -798,7 +930,7 @@
"message": "游戏启动时的窗口宽度。"
},
"instance.settings.tabs.window.width.enter": {
"message": "输入宽度…"
"message": "输入宽度…"
},
"instance.worlds.a_minecraft_server": {
"message": "Minecraft 服务器"
@@ -809,6 +941,9 @@
"instance.worlds.copy_address": {
"message": "复制地址"
},
"instance.worlds.create_shortcut": {
"message": "创建快照"
},
"instance.worlds.dont_show_on_home": {
"message": "在主界面隐藏"
},
@@ -897,7 +1032,7 @@
"message": "仍然安装"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "恶意软件常常通过模组包文件在 Discord 等平台上分享而传播。"
"message": "恶意软件常常通过整合包文件在 Discord 等平台上分享而传播。"
},
"unknown-pack-warning-modal.warning.body": {
"message": "我们在 Modrinth 上找不到此文件。强烈建议你仅从可信来源安装文件。"

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