Compare commits

...
Author SHA1 Message Date
Calum H. (IMB11) 6efabff0d6 feat: screenshots tab draft 2026-08-13 16:06:40 +01:00
Sychic 290bd2b934 chore: more idea sources (#7122) 2026-08-13 02:41:31 +02:00
Sychic 9ecbe8eaa6 feat(labrinth): granular disclosure locking (#7108)
* feat(labrinth): granular disclosure locking

* chore(labrinth): update query cache
2026-08-12 17:26:15 -07:00
Sychic 549b333eb2 feat(labrinth): project disclosures (#6945)
* feat(labrinth): project disclosures model

* feat(labrinth): project disclosures database model

* feat(labrinth): project disclosures get endpoint

* feat(labrinth): censor user ids if set by moderator

* feat(labrinth): wrap disclosures in struct

* feat(labrinth): edit project disclosures endpoint

* style(labrinth): cargo fmt

* style(labrinth): fix typo

* feat(labrinth): add fields for ai content disclosure

* fix(labrinth): field typo

* chore(labrinth): update query cache

* feat(labrinth): index disclosures in search

* refactor(labrinth): use enum instead of bools

* feat(labrinth): trigger incremental index on disclosure change

* feat(labrinth): change archived status to disclosure

* feat(labrinth): use disclosure for archival status

* fix(labrinth): error type for deserialization

* fix(labrinth): migration timestamp

* fix(labrinth): add disclosures to elasticsearch schema

* fix(labrinth): don't remove archival disclosure when changing status via v3 api

* feat(labrinth): derive str for ai usages and telemtry consent

* feat(labrinth): include ai usages and telemetry consent in disclosure types

* refactor(labrinth): move disclosure parsing to document creation

* fix(labrinth): prevent removing moderator added archival disclosures

* fix(labrinth): dedupe ai usages

* fix(labrinth): auth logic on archived disclosure removal

* feat(labrinth): add interactions field

* style(labrinth): cargo fmt

* feat(labrinth): soft delete disclosures

* feat(labrinth): return soft-deleted disclosures

* fix(labrinth): update disclosure on removal

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-08-12 17:26:14 -07:00
Prospector 4b82cca3bf fix sidebar not showing on user pages (#7097)
* fix sidebar not showing on user pages

* prepr
2026-08-12 17:25:26 -07:00
Prospector 734fd9cdb3 fix app build on windows & mac (#7118) 2026-08-12 17:25:15 -07:00
Prospector 2fd4495104 rejected projects go to ghost on delete (#7107)
* draft: rejected projects go to ghost on delete

* don't free slug

* fix sqlx

* free slugs but different

* add auto reject for processing items given to ghost, as well as send thread messages with a little info on who it came from
2026-08-12 23:54:57 +00:00
François-Xavier Talbot 552bc3f739 fix(labrinth): webhook scopes (#7114)
fix(labrinth): scope paypal & tremendous webhooks in payout scope
2026-08-12 18:34:33 +02:00
Truman Gao 4758cabbf9 feat: add hide and show download items (#7106)
* feat: add hide and show download items

* pnpm prepr
2026-08-12 16:18:02 +00:00
aecsocket 6ec979a076 fix: hide ad webview when showing fullscreen content (#7110)
* fix: hide ad webview when showing fullscreen content

* fixes
2026-08-12 16:02:02 +00:00
aecsocket c34bd26f87 fix: search indexing and schema changes (#7090)
* fix: incremental indexing uses latest db data

* dependent search fields
2026-08-12 11:54:35 -04:00
aecsocket 2a43792fd9 fix: app pinging servers over IPv6 (#7100)
fix: app pinging servers opver IPv6
2026-08-11 12:58:59 -04:00
Calum H. 2d567b93ce feat: imgur proxying for blocked countries (#7104) 2026-08-11 16:40:03 +00:00
Prospector 26e05ee9e5 update API docs to add environment fields (#7096)
* update API docs to add environment fields

* fix unknown on create/patch routes
2026-08-10 14:30:00 -07:00
jade ca8c5a717f fix: unique preview toggle ID in MarkdownEditor (#7093)
fix: preview toggle in MD editor targets other MD editors
2026-08-10 18:47:33 +00:00
Prospector 759f67a551 changelog 2026-08-10 11:17:45 -07:00
Prospector 66628fd69d add reddit and youtube as socials (#7092) 2026-08-10 11:17:13 -07:00
Prospector 1ffcd67562 changelog 2026-08-10 10:36:01 -07:00
Calum H. fda5c62bc6 fix: ignore extensions for mrpack warning modal (#6948) 2026-08-10 17:25:52 +00:00
aecsocket f8f05ce91b fix: expose raw icon URL in project response (#7086)
* fix: expose raw icon URL in project response

* remove unnecessary test
2026-08-10 15:48:23 +00:00
Calum H.andProspector 8b2438aebb fix: Gifs in the project description are not working properly (#7076)
* fix: Gifs in the project description are not working properly
Fixes #3786

* prepr

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-08-10 15:43:13 +00:00
aecsocket 07bac8ffde fix: v3 environment fields in all v2 endpoints (#7082) 2026-08-10 15:42:56 +00:00
Calum H. d0856d46f2 fix: copy on unknown file warning modal (#7078)
Closes: #6865
2026-08-10 15:26:23 +00:00
Calum H. e8aee15664 chore: cleanup standards (#6970)
* chore: cleanup standards & skills

* remove: figma mcp doc, not needed anymore
2026-08-10 15:15:44 +00:00
Calum H. 98cb6b6b44 fix: buttons qa (#7067)
* fix: broken minecraft required modal button

* fix: app close icon issue

* fix: versions tooltip missing

* fix: instance not found bug on btn press
2026-08-10 15:15:27 +00:00
coolbot f27387462e Moderation: Rule placeholders with subsections & anchors, update messages. (#7068)
Rule placeholders with subsections & anchors, update messages.
2026-08-10 14:31:16 +00:00
coolbot ef90f08813 Moderation: Metadata Message (#7066)
* game versions msg

* sneak in some tec rev quick reply updates

* add loaders msg to metadata stage

* fix loaders msg
2026-08-10 14:31:03 +00:00
Calum H. 9fd45ef2ce fix: always check for updates/clear cache 10 min cooldown (#7077) 2026-08-10 14:00:42 +00:00
Michael H. 92971a3e0d build: don't auto update staging 2026-08-10 14:57:05 +02:00
Modrinth Bot 357224f22a New translations from Crowdin (main) (#7071) 2026-08-10 11:43:08 +00:00
Seungyeop Lee e2612d1a33 fix sidebar closing when collapsing friends list sections (#7060) (#7062) 2026-08-10 11:32:18 +00:00
coolbot 318840e403 Fix: Version nag navigation (#7070)
* fix: version nag navigation

* prepr
2026-08-10 10:54:29 +00:00
306 changed files with 9376 additions and 2809 deletions
+23
View File
@@ -0,0 +1,23 @@
---
name: api-module
description: Add an API endpoint module to packages/api-client from an OpenAPI schema. Use for new backend endpoints, API client modules, or tasks that provide an OpenAPI schema.
---
# Add an API Module
Read the applicable `AGENTS.md` files before you edit code.
Read [the API module standard](../../../standards/frontend/ADDING_API_MODULES.md) in full.
1. Identify the OpenAPI schema from the request. If more than one schema is possible, ask the user to select one.
2. Read the schema. Identify each endpoint, HTTP method, request type, response type, and path parameter.
3. Get the service and version from the URL prefix. For example, map `/v3/projects` to `labrinth/v3/`.
4. Define the API types in `types.ts`. Make each type match the schema exactly.
5. Do not change, rename, or remove API fields.
6. Make a module class that extends `AbstractModule`. Implement each endpoint with `this.client.request()` or `this.client.upload()`.
7. Use the request-option pattern from the standard. Do not call `$fetch`, `fetch`, or another HTTP client directly.
8. Add the module to `MODULE_REGISTRY` so the client can instantiate it.
9. Export new service types from the applicable barrel `index.ts`.
10. Check the module paths, registry key, public type exports, and endpoint types.
Run only the checks that the user or the applicable `AGENTS.md` permits.
@@ -0,0 +1,4 @@
interface:
display_name: "Add API Module"
short_description: "Add typed API client modules from OpenAPI"
default_prompt: "Use $api-module to add an API client module from this OpenAPI schema."
@@ -0,0 +1,38 @@
---
name: cross-platform-pages
description: Convert a page to the shared Modrinth page system for the website and desktop app. Use for shared layouts, wrapped layouts, or platform dependency-injection contracts.
---
# Convert a Cross-Platform Page
Read the applicable `AGENTS.md` files before you edit code.
Read these standards in full:
- [Cross-platform pages](../../../standards/frontend/CROSS_PLATFORM_PAGES.md)
- [Dependency injection](../../../standards/frontend/DEPENDENCY_INJECTION.md)
1. Identify the target page from the request.
2. Read the page and its route shell. Identify data sources, mutations, navigation, and platform APIs.
3. Use a wrapped layout when both platforms use the same API source and page logic.
4. Use a shared layout when platform data or operations have different implementations.
For a shared layout:
1. Define a provider contract for all platform operations.
2. Put common UI and state logic in the shared layout.
3. Put reusable search, filter, and selection logic in local composables.
4. Implement the contract in `apps/frontend/` and `apps/app-frontend/`.
5. Use optional contract fields only for capabilities that are not available on both platforms.
For a wrapped layout:
1. Move the page to `packages/ui/src/layouts/wrapped/` and preserve its route structure.
2. Replace platform-only imports with common utilities or provider calls.
3. Make each frontend route shell render the wrapped component.
4. Match primary query options in both route shells when the layout uses `ReadyTransition` and `useReadyState`.
5. Prefetch these queries with `ensureQueryData`, as the standard specifies.
Check that both route shells resolve their imports. Check that all required provider fields have implementations.
Run only the checks that the user or the applicable `AGENTS.md` permits.
@@ -0,0 +1,4 @@
interface:
display_name: "Convert Cross-Platform Page"
short_description: "Share pages across the web and desktop app"
default_prompt: "Use $cross-platform-pages to convert this page for the website and desktop app."
+21
View File
@@ -0,0 +1,21 @@
---
name: figma-mcp
description: Convert a Figma design into a Modrinth Vue page or component. Use when a request provides a Figma URL or asks to implement a Figma layout.
---
# Implement a Figma Design
Read the applicable `AGENTS.md` files before you edit code.
Read `packages/ui/AGENTS.md` in full.
1. Load the available Figma design-to-code instructions and follow the MCP tool guidance.
2. Call `get_design_context` first with `clientLanguages: "typescript,html,css"` and `clientFrameworks: "vue"`.
3. Treat the result as reference code and adapt it to the Modrinth codebase.
4. Map Figma color variables to the applicable `surface-*` and `text-*` tokens. Do not use aliased Figma names directly.
5. Reuse applicable components from `packages/ui/src/components/` before creating new ones. Also refer to `standards/frontend/COMPONENT_STRUCTURE.md`
6. Read `packages/assets/styles/variables.scss` when Figma does not supply a required token.
7. Use exact spacing values from the design.
8. Implement the result as a Vue SFC with Tailwind classes and the existing component library.
Run only the checks that the user or the applicable `AGENTS.md` permits.
@@ -0,0 +1,4 @@
interface:
display_name: "Implement Figma Design"
short_description: "Build Modrinth Vue UI from Figma designs"
default_prompt: "Use $figma-mcp to implement this Figma design as a Modrinth Vue component."
+32
View File
@@ -0,0 +1,32 @@
---
name: i18n-pass
description: Convert hard-coded English text in changed Vue components to the @modrinth/ui localization system. Use for an i18n pass, untranslated-string review, pull request, or component migration.
---
# Do an Internationalization Pass
Read the applicable `AGENTS.md` files before you edit code.
Read [the internationalization standard](../../../standards/frontend/INTERNATIONALIZATION.md) in full.
1. Identify the scope from the request.
2. For a pull request, use `gh pr diff <number>` to identify changed files.
3. For a file path, inspect that file.
4. When the request gives no scope, inspect the current uncommitted diff.
5. Limit the pass to changed `.vue` files.
6. Find user-visible text in templates and scripts.
Check inner text, `alt`, `placeholder`, `aria-label`, buttons, tooltips, notifications, dropdown labels, and error messages.
Do not change dynamic expressions, HTML tag names, CSS classes, internal identifiers, or log messages.
1. Define stable message IDs with `defineMessage` or `defineMessages`.
2. Replace simple text with `formatMessage()` calls.
3. Use `<IntlFormatted>` for text that contains links or markup.
4. Use ICU selections and plurals when grammar depends on a value.
5. Add a space before `}}` when an ICU placeholder ends at the Vue delimiter.
6. Do not change component logic, layout, or reactivity.
7. Do not edit localization JSON files. The user maintains those files.
8. Check the changed templates again for hard-coded English text.
Run only the checks that the user or the applicable `AGENTS.md` permits.
@@ -0,0 +1,4 @@
interface:
display_name: "Run Internationalization Pass"
short_description: "Localize user-visible text in Vue files"
default_prompt: "Use $i18n-pass to localize the user-visible text in these changed Vue files."
+40
View File
@@ -0,0 +1,40 @@
---
name: review-changelog
description: Review the latest packages/blog/changelog.ts entry against the Modrinth changelog standard. Use before a pull request or when asked to review or lint a changelog entry.
---
# Review a Changelog Entry
Read [the changelog standard](../../../standards/maintaining/CHANGELOG.md) in full before the review.
1. Open `packages/blog/changelog.ts`.
2. Find the first entry in the `VERSIONS` array.
3. If the request names `web`, `hosting`, or `app`, review the latest entry for that product.
4. Otherwise, review the latest entry and all adjacent entries with the same date.
Check the entry structure:
- `date` contains a valid ISO 8601 timestamp.
- `product` is `web`, `hosting`, or `app`.
- An `app` entry has a `version` value.
- A `web` or `hosting` entry does not have a `version` value.
- Standard headings are `## Added`, `## Changed`, `## Fixed`, and `## Security`.
- A featured release can use a linked heading.
- Flag the legacy `## Improvements` heading.
Check each bullet:
- The voice and tense agree with the section.
- The first verb agrees with the section.
- The bullet describes user-visible behavior, not implementation.
- The bullet identifies the applicable page, tab, modal, or feature.
- The bullet contains one sentence, uses sentence case, and ends with a period.
- Product and UI names use the public labels.
- The bullet does not contain filler, vague intensifiers, apologies, or internal references.
- The bullet is not a duplicate detail of a larger listed change.
Group findings by entry. For each finding, show the original bullet and a proposed replacement.
If the entry has no findings, state this result. Do not edit the changelog unless the user asks you to apply fixes.
When the user asks for fixes, preserve tab indentation and template-literal formatting.
@@ -0,0 +1,4 @@
interface:
display_name: "Review Changelog"
short_description: "Review changelog entries for style problems"
default_prompt: "Use $review-changelog to review the latest changelog entry."
+39
View File
@@ -0,0 +1,39 @@
---
name: tanstack-query
description: Convert Vue server-state code to TanStack Query. Use for useQuery, useMutation, cache invalidation, optimistic updates, or replacement of useAsyncData and manual ref patterns.
---
# Convert Data Code to TanStack Query
Read the applicable `AGENTS.md` files before you edit code.
Read [the TanStack Query standard](../../../standards/frontend/FETCHING_DATA.md) in full.
1. Identify the target file from the request.
2. Find `useAsyncData`, `useFetch`, manual API refs, and fetch calls in `onMounted`.
3. Identify mutations that use manual loading, error, or result refs.
For queries:
1. Replace manual fetch logic with `useQuery`.
2. Get `api-client` with `injectModrinthClient()`.
3. Use a hierarchical query key with the resource, qualifier, and parameters.
4. Use a computed query key for reactive parameters.
5. Use a computed `enabled` option when the query depends on other data.
6. Use a shared query-option factory when multiple components use the query.
For mutations:
1. Replace manual mutation state with `useMutation`.
2. Invalidate or update related query data after success.
3. Use an optimistic update only when the UI needs an immediate response.
4. Cancel the applicable query and save its prior data before an optimistic update.
5. Restore the prior data after an error. Invalidate the query after settlement.
Remove manual loading and error refs that TanStack Query replaces. Remove obsolete `onMounted` fetch calls.
Keep Nuxt SSR behavior. Match route-shell prefetch options when `ReadyTransition` and `useReadyState` depend on the query.
Check query keys, invalidation prefixes, reactive values, and rollback data.
Run only the checks that the user or the applicable `AGENTS.md` permits.
@@ -0,0 +1,4 @@
interface:
display_name: "Migrate to TanStack Query"
short_description: "Migrate Vue server state to TanStack Query"
default_prompt: "Use $tanstack-query to migrate this Vue component to TanStack Query."
-18
View File
@@ -1,18 +0,0 @@
---
name: api-module
description: Add a new API endpoint module to packages/api-client from an OpenAPI schema. Use when adding new backend endpoints, creating API client modules, or when an openapi.yml is provided.
argument-hint: <path-to-openapi.yml>
---
Refer to the standard: @standards/frontend/ADDING_API_MODULES.md
## Steps
1. **Read the OpenAPI schema** at `$ARGUMENTS` — identify the endpoints, request/response shapes, and path parameters.
2. **Read the standard above** for naming conventions, type rules, and the module registration pattern.
3. **Determine the service and version** — the URL path prefix tells you which service directory and version namespace to use (e.g. `/v3/projects``labrinth/v3/`).
4. **Define types in `types.ts`** — types must match the API response 1:1. Use the OpenAPI schema as the source of truth. Do not reshape or rename fields.
5. **Create the module class** — extend `BaseModule`, implement each endpoint as a method. Use the correct HTTP verb and request options pattern from the standard.
6. **Register in `MODULE_REGISTRY`** — add the module entry so it's auto-instantiated on the client.
7. **Export types** from the service's barrel `index.ts`.
8. **Verify** — check that the module compiles and the types are accessible from `@modrinth/api-client`.
@@ -1,26 +0,0 @@
---
name: cross-platform-pages
description: Convert a page to the cross-platform page system so it works in both the website and the desktop app. Use when moving a page into packages/ui/src/layouts/, creating shared or wrapped layouts, or setting up DI contracts for platform abstraction.
argument-hint: <path-to-page>
---
Refer to the standards: @standards/frontend/CROSS_PLATFORM_PAGES.md and @standards/frontend/DEPENDENCY_INJECTION.md
## Steps
1. **Read the target page** at `$ARGUMENTS` and understand its data sources, mutations, and navigation.
2. **Read the standards above** to understand the shared vs wrapped distinction and the DI pattern.
3. **Decide the category:**
- **Wrapped** (`layouts/wrapped/`) — if the page uses the same API source on both platforms (e.g. web requests, not Tauri plugins). Just move the page component into `packages/ui` and import it from both frontends.
- **Shared** (`layouts/shared/`) — if the page has different data-fetching logic per platform (e.g. website uses `api-client`, app uses Tauri `invoke`). Requires a DI contract.
4. **For shared layouts:**
- Define a DI contract interface in `providers/` capturing all platform-specific operations.
- Create the layout component that injects the context and handles all UI logic.
- Extract reusable stateful logic (search, filtering, selection) into `composables/`.
- Implement the contract separately in each frontend (`apps/frontend/`, `apps/app-frontend/`).
5. **For wrapped pages:**
- Move the page component into `packages/ui/src/layouts/wrapped/` matching the route structure.
- Replace any platform-specific imports with shared utilities.
- Import and render the wrapped page from both frontends as a simple component.
- If the layout uses TanStack Query for initial route paint with `ReadyTransition` / `useReadyState`, each platform route shell must call `ensureQueryData` for those queries with matching keys and fetchers — see **Platform route shells: prefetch with `ensureQueryData`** in `standards/frontend/CROSS_PLATFORM_PAGES.md`.
6. **Verify** the page renders correctly by checking for missing imports and that all DI contracts are satisfied.
-22
View File
@@ -1,22 +0,0 @@
---
name: figma-mcp
description: Use the Figma MCP server to translate a Figma design into a Vue page or component layout. Use when the user provides a Figma URL, asks to implement a design, or wants to draft a page layout from Figma.
argument-hint: <figma-url>
---
Refer to the standard: @standards/frontend/FIGMA_MCP_USAGE.md
Also read @packages/ui/CLAUDE.md for color token mapping and component conventions.
## Steps
1. **Parse the Figma URL** from `$ARGUMENTS` — extract the `fileKey` and `nodeId`. Convert `-` to `:` in the node ID.
2. **Read the standards above** for the available tools, adaptation rules, and color usage.
3. **Call `get_design_context`** with the extracted `nodeId` and `fileKey`, using `clientLanguages: "typescript,html,css"` and `clientFrameworks: "vue"`. This is always the first tool to call.
5. **Adapt the output to the Modrinth codebase:**
- Map Figma color variables to `surface-*` / `text-*` tokens — never use Figma's aliased names directly.
- Check `packages/ui/src/components/` for existing components that match elements in the design (buttons, cards, modals, inputs, etc.).
- Check `packages/assets/styles/variables.scss` for tokens not exposed in Figma.
- Match spacing values exactly from the design.
6. **Use `get_screenshot`** if you need a closer visual reference of specific nodes.
7. **Use `get_variable_defs`** to verify which design tokens are applied to ambiguous elements.
8. **Build the component** as a Vue SFC using Tailwind classes and the project's existing component library.
-24
View File
@@ -1,24 +0,0 @@
---
name: i18n-pass
description: Perform an i18n localization pass on changed files or a pull request, converting hard-coded English strings to the @modrinth/ui i18n system. Use when internationalizing a set of changes, reviewing a PR for untranslated strings, or converting a specific component.
argument-hint: [file-path-or-pr-number]
---
Refer to the standard: @standards/frontend/INTERNATIONALIZATION.md
## Steps
1. **Identify the scope of changes:**
- If `$ARGUMENTS` is a PR number, run `gh pr diff $ARGUMENTS` to get the changed files.
- If `$ARGUMENTS` is a file path, use that directly.
- If no argument, check `git diff` for uncommitted changes.
2. **Read the standard above** for the message definition pattern, ICU format rules, and `IntlFormatted` usage.
3. **Filter to Vue SFCs** — only `.vue` files need i18n passes. Skip non-component files.
4. **For each file, scan for hard-coded strings:**
- `<template>`: inner text, `alt`, `placeholder`, `aria-label`, button labels, tooltip text.
- `<script>`: string literals passed to user-visible UI (notification messages, dropdown labels, error messages).
- Skip: dynamic expressions, HTML tag names, CSS classes, internal identifiers, log messages.
5. **Define messages** with `defineMessages` — use descriptive, stable `id`s based on the component's domain (e.g. `project.settings.title`).
6. **Replace strings in templates** with `formatMessage()` calls, or `<IntlFormatted>` for strings containing links or markup.
7. **Handle ICU edge cases** — add a space before `}}` if an ICU placeholder ends at a Vue template delimiter boundary.
8. **Verify** no hard-coded English strings remain in the changed templates. Do not alter logic, layout, or reactivity.
-36
View File
@@ -1,36 +0,0 @@
---
name: review-changelog
description: Review the latest changelog entry in packages/blog/changelog.ts against the project's changelog style guide and flag bullets that need rewriting. Use when checking a freshly added changelog entry before opening a PR, or when the user asks to review/lint the latest changelog.
argument-hint: [product?]
---
Refer to the standard: @standards/maintaining/CHANGELOG.md
## Steps
1. **Locate the latest entry:**
- Open `packages/blog/changelog.ts`.
- The latest entries are at the top of the `VERSIONS` array.
- If `$ARGUMENTS` specifies a product (`web`, `hosting`, `app`), review the most recent entry for that product. Otherwise, review the most recent entry overall, plus any sibling entries sharing the same `date` (coordinated releases ship together).
2. **Read the standard above** in full before reviewing. The bullet rules, section/verb agreement, and "Don't" list are the source of truth.
3. **Check the entry shell:**
- `date` is a valid ISO 8601 timestamp.
- `product` is one of `web`, `hosting`, `app`.
- `version` is present for `app` entries and omitted for `web`/`hosting`.
- Section headings use `## Added`, `## Changed`, `## Fixed`, `## Security` (or a featured-release linked heading). Flag legacy `## Improvements`.
4. **Review each bullet** against the standard. For each bullet, check:
- Voice/tense matches the section heading.
- Opening verb agrees with its section.
- Describes observable behavior, not implementation.
- Specific enough to identify the surface (names the tab/page/modal).
- One sentence, ends with a period, sentence case.
- Uses branded names (Modrinth App, Modrinth Hosting) correctly.
- No filler ("issue with", "issue where", "various", "some"), no vague intensifiers, no apologies, no PR/commit references (unless crediting a third-party contributor with a linked GitHub profile).
- Not a duplicate sub-fix of a bigger change already listed.
5. **Report findings** as a short list grouped by entry. For each problem bullet, show the original line and a suggested rewrite. If the entry is clean, say so explicitly. Do not edit the file unless the user asks - this skill is a review pass, not a rewrite pass.
6. **If the user then asks to apply fixes**, edit `packages/blog/changelog.ts` directly using the suggested rewrites. Preserve tab indentation and template literal formatting.
-27
View File
@@ -1,27 +0,0 @@
---
name: tanstack-query
description: Convert a page or component from useAsyncData/manual ref patterns to TanStack Query for server state management. Use when migrating data fetching to useQuery/useMutation, adding cache invalidation, or replacing useAsyncData with TanStack Query.
argument-hint: <path-to-file>
---
Refer to the standard: @standards/frontend/FETCHING_DATA.md
## Steps
1. **Read the target file** at `$ARGUMENTS` and identify all data-fetching patterns: `useAsyncData`, `useFetch`, manual `ref()` + `await`, or `onMounted` fetch calls.
2. **Read the standard above** for the query/mutation patterns, query key conventions, and optimistic update approach.
3. **Convert queries:**
- Replace `useAsyncData` / `useFetch` / manual fetches with `useQuery`.
- Use the `api-client` via `injectModrinthClient()` for the `queryFn`.
- Design query keys with the `['resource', 'version', ...params]` convention.
- Use `computed` query keys for reactive parameters.
- Use the `enabled` option for conditional queries that depend on other data.
4. **Convert mutations:**
- Replace manual `try/catch` + `ref` patterns with `useMutation`.
- Add `onSuccess` handlers that invalidate or update related query caches.
- Consider optimistic updates for UI-critical mutations (follow the pattern in the standard).
5. **Clean up:**
- Remove manual loading/error `ref()`s that are now handled by TanStack Query's return values (`isPending`, `isError`, `error`).
- Remove manual `onMounted` fetch calls.
- Ensure SSR compatibility — queries in Nuxt pages are automatically awaited during SSR.
6. **Verify** the page still renders correctly and that cache invalidation triggers re-fetches where expected.
+1 -1
View File
@@ -147,7 +147,7 @@ jobs:
deploy:
needs: [skip-if-clean, docker-build]
if: ${{ needs.skip-if-clean.outputs.internal == 'true' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/prod') }}
if: ${{ needs.skip-if-clean.outputs.internal == 'true' && github.ref == 'refs/heads/prod' }}
uses: SparkUniverse/workflows/.github/workflows/argo-update.yaml@main
secrets:
ARGOCD_DEPLOY_KEY: ${{ secrets.ARGOCD_DEPLOY_KEY }}
+1 -2
View File
@@ -65,8 +65,7 @@ generated
app-playground-data/*
.astro
.claude/*
!.claude/skills/
.claude/
.letta
# labrinth demo fixtures
+11
View File
@@ -17,6 +17,17 @@
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-util/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/muralpay/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/labrinth-derive/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/neverbounce/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/serde-binhum/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/xredis/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
-1
View File
@@ -1 +0,0 @@
CLAUDE.md
+93
View File
@@ -0,0 +1,93 @@
# Modrinth Monorepo
This is the Modrinth monorepo — it contains all Modrinth projects, both frontend and backend. When entering a project, either to edit or analyse, you should read its AGENTS.md.
## Architecture
- **Monorepo tooling:** [Turborepo](https://turbo.build/) (`turbo.jsonc`) + [pnpm workspaces](https://pnpm.io/workspaces) (`pnpm-workspace.yaml`)
- **Frontend:** Vue 3 / Nuxt 3, Tailwind CSS v3
- **Backend:** Rust (Labrinth API), Postgres, Clickhouse
- **Indentation:** Use TAB everywhere, never spaces
### Apps (`apps/`)
| App | Description |
| ----------------- | ------------------------------ |
| `frontend` | Main Modrinth website (Nuxt 3) |
| `app-frontend` | Desktop/app frontend (Vue 3) |
| `app` | Desktop/app shell (Tauri) |
| `app-playground` | Testing playground for app |
| `labrinth` | Backend API service |
| `daedalus_client` | Daedalus client implementation |
| `docs` | Documentation site (Astro) |
### Packages (`packages/`)
| Package | Description |
| ------------------ | ----------------------------------------------------- |
| `ui` | Shared Vue component library (`@modrinth/ui`) |
| `assets` | Styling and auto-generated icons (`@modrinth/assets`) |
| `api-client` | API client for Nuxt, Tauri, and Node/browser |
| `app-lib` | Shared app library |
| `blog` | Blog system and changelog data |
| `utils` | Shared utility functions (mostly deprecated) |
| `moderation` | Moderation utilities |
| `daedalus` | Daedalus protocol |
| `tooling-config` | ESLint, Prettier, TypeScript configs |
| `ariadne` | Analytics library |
| `modrinth-log` | Logging utilities |
| `modrinth-maxmind` | MaxMind GeoIP |
| `modrinth-util` | General utilities |
| `muralpay` | Payment processing |
| `path-util` | Path utilities |
| `sqlx-tracing` | SQLx query tracing |
## Pre-PR Commands
Run these from the **root** folder before opening a pull request - do not run these after each prompt the user gives you, only run when asked, ask the user a question if they want to run it if the user indicates that they are about to create a pull request.
- **Website:** `pnpm prepr:frontend:web`
- **App frontend:** `pnpm prepr:frontend:app`
- **Frontend libs:** `pnpm prepr:frontend:lib`
- **All frontend (app+web):** `pnpm prepr`
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
The website and app `prepr` commands
## Dev Commands
- **Website:** `pnpm web:dev` (copy `.env` template in `apps/frontend/` first)
- **App:** `pnpm app:dev` (copy `.env` template in `packages/app-lib/` first)
- **Storybook (packages/ui):** `pnpm storybook`
## Project-Specific Instructions
Each project may have its own file with detailed instructions:
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
- [`apps/frontend/AGENTS.md`](apps/frontend/AGENTS.md) - Frontend Website
## Code Guidelines
### Comments
- DO NOT use "heading" comments like: `=== Helper methods ===`.
- Use doc comments, but avoid inline comments unless ABSOLUTELY necessary for clarity. Code should aim to be self documenting!
## Bash Guidelines
### Output handling
- DO NOT pipe output through `head`, `tail`, `less`, or `more`
- NEVER use `| head -n X` or `| tail -n X` to truncate output
- IMPORTANT: Run commands directly without pipes when possible
- IMPORTANT: If you need to limit output, use command-specific flags (e.g. `git log -n 10` instead of `git log | head -10`)
- ALWAYS read the full output — never pipe through filters
### General
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
- For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc.
- Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client`
- When provided problems, do not say "I didn't introduce these problems" (shifting the blame/effort) - just fix them.
## Standards
Standards available at the @standards/ folder.
-110
View File
@@ -1,110 +0,0 @@
# Modrinth Monorepo
This is the Modrinth monorepo — it contains all Modrinth projects, both frontend and backend. When entering a project, either to edit or analyse, you should read it's CLAUDE.md.
## Architecture
- **Monorepo tooling:** [Turborepo](https://turbo.build/) (`turbo.jsonc`) + [pnpm workspaces](https://pnpm.io/workspaces) (`pnpm-workspace.yaml`)
- **Frontend:** Vue 3 / Nuxt 3, Tailwind CSS v3
- **Backend:** Rust (Labrinth API), Postgres, Clickhouse
- **Indentation:** Use TAB everywhere, never spaces
### Apps (`apps/`)
| App | Description |
| ----------------- | ------------------------------ |
| `frontend` | Main Modrinth website (Nuxt 3) |
| `app-frontend` | Desktop/app frontend (Vue 3) |
| `app` | Desktop/app shell (Tauri) |
| `app-playground` | Testing playground for app |
| `labrinth` | Backend API service |
| `daedalus_client` | Daedalus client implementation |
| `docs` | Documentation site (Astro) |
### Packages (`packages/`)
| Package | Description |
| ------------------ | ----------------------------------------------------- |
| `ui` | Shared Vue component library (`@modrinth/ui`) |
| `assets` | Styling and auto-generated icons (`@modrinth/assets`) |
| `api-client` | API client for Nuxt, Tauri, and Node/browser |
| `app-lib` | Shared app library |
| `blog` | Blog system and changelog data |
| `utils` | Shared utility functions (mostly deprecated) |
| `moderation` | Moderation utilities |
| `daedalus` | Daedalus protocol |
| `tooling-config` | ESLint, Prettier, TypeScript configs |
| `ariadne` | Analytics library |
| `modrinth-log` | Logging utilities |
| `modrinth-maxmind` | MaxMind GeoIP |
| `modrinth-util` | General utilities |
| `muralpay` | Payment processing |
| `path-util` | Path utilities |
| `sqlx-tracing` | SQLx query tracing |
## Pre-PR Commands
Run these from the **root** folder before opening a pull request - do not run these after each prompt the user gives you, only run when asked, ask the user a question if they want to run it if the user indicates that they are about to create a pull request.
- **Website:** `pnpm prepr:frontend:web`
- **App frontend:** `pnpm prepr:frontend:app`
- **Frontend libs:** `pnpm prepr:frontend:lib`
- **All frontend (app+web):** `pnpm prepr`
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
The website and app `prepr` commands
## Dev Commands
- **Website:** `pnpm web:dev` (copy `.env` template in `apps/frontend/` first)
- **App:** `pnpm app:dev` (copy `.env` template in `packages/app-lib/` first)
- **Storybook (packages/ui):** `pnpm storybook`
## Project-Specific 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
## Code Guidelines
### Comments
- DO NOT use "heading" comments like: `=== Helper methods ===`.
- Use doc comments, but avoid inline comments unless ABSOLUTELY necessary for clarity. Code should aim to be self documenting!
## Bash Guidelines
### Output handling
- DO NOT pipe output through `head`, `tail`, `less`, or `more`
- NEVER use `| head -n X` or `| tail -n X` to truncate output
- IMPORTANT: Run commands directly without pipes when possible
- IMPORTANT: If you need to limit output, use command-specific flags (e.g. `git log -n 10` instead of `git log | head -10`)
- ALWAYS read the full output — never pipe through filters
### General
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
- For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc.
- Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client`
- When provided problems, do not say "I didn't introduce these problems" (shifting the blame/effort) - just fix them.
## Edit Tool - Whitespace Handling (CLAUDE ONLY)
The Read tool uses `→` to mark where line numbers end and file content begins.
**Rule:** Copy the EXACT whitespace that appears after the `→` marker.
- Whatever appears between `→` and the code text is what's actually in the file
- That whitespace must be used EXACTLY in Edit tool's old_string
- Don't count arrows, don't interpret - just copy what's after the `→`
**Example:**
14→ private byte tag;
For Edit, use: ` private byte tag;` (copy everything after →, including the two tabs)
**If Edit fails:** Stop and explain the problem. Do not attempt sed/awk/bash workarounds.
**IMPORTANT**: Trust the Read tool output. Copy what's after `→` into Edit immediately. DO NOT verify with sed/od/grep first - that's wasting time and the instructions already tell you to stop if Edit fails, not to pre-verify.
## Standards
Standards available at the @standards/ folder.
+40 -9
View File
@@ -95,8 +95,9 @@ import {
hide_ads_window,
init_ads_window,
perform_ads_consent_action,
release_ads_window_hold,
should_show_ads_consent_popup,
show_ads_window,
take_ads_window_hold,
} from '@/helpers/ads.js'
import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
@@ -166,6 +167,25 @@ function updateHistoryNavigationState() {
canNavigateForward.value = historyState?.forward != null
}
let fullscreenAdsWindowHold = false
async function handleFullscreenChange() {
const fullscreen = document.fullscreenElement !== null
if (fullscreen === fullscreenAdsWindowHold) return
fullscreenAdsWindowHold = fullscreen
try {
if (fullscreen) {
await take_ads_window_hold()
} else {
await release_ads_window_hold()
}
} catch (error) {
fullscreenAdsWindowHold = !fullscreen
handleError(error)
}
}
updateHistoryNavigationState()
const APP_LEFT_NAV_WIDTH = '4rem'
@@ -175,11 +195,17 @@ const PRIDE_FUNDRAISER_END_DATE = new Date('2026-07-01T00:00:00Z').getTime()
const credentials = ref()
let credentialsRefreshId = 0
const sidebarToggled = ref(true)
const unsubscribeSidebarToggle = themeStore.$subscribe(() => {
sidebarToggled.value = !themeStore.toggleSidebar
})
watch(
() => themeStore.toggleSidebar,
(toggleSidebar) => {
sidebarToggled.value = !toggleSidebar
},
)
const forceSidebar = computed(
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
() =>
route.path.startsWith('/browse') ||
route.path.startsWith('/project') ||
route.path.startsWith('/user'),
)
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
@@ -281,8 +307,8 @@ providePageContext({
})
provideModalBehavior({
noblur: computed(() => !themeStore.advancedRendering),
onShow: () => hide_ads_window(),
onHide: () => show_ads_window(),
onShow: () => take_ads_window_hold(),
onHide: () => release_ads_window_hold(),
})
const {
@@ -297,7 +323,7 @@ const {
setModpackAlreadyInstalledModal,
handleModpackDuplicateCreateAnyway,
handleModpackDuplicateGoToInstance,
} = setupProviders(notificationManager, popupNotificationManager)
} = setupProviders(tauriApiClient, notificationManager, popupNotificationManager)
const news = ref([])
const displayedServerInviteNotifications = new Set()
@@ -357,6 +383,7 @@ onMounted(async () => {
document.querySelector('body').addEventListener('click', handleClick)
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
document.addEventListener('fullscreenchange', handleFullscreenChange)
checkUpdates()
})
@@ -364,9 +391,13 @@ onMounted(async () => {
onUnmounted(async () => {
document.querySelector('body').removeEventListener('click', handleClick)
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
unsubscribeSidebarToggle()
document.removeEventListener('fullscreenchange', handleFullscreenChange)
clearDelayedUpdatePopup()
if (fullscreenAdsWindowHold) {
fullscreenAdsWindowHold = false
await release_ads_window_hold().catch(handleError)
}
await unlistenAdsConsent?.()
await unlistenUpdateDownload?.()
})
@@ -1,15 +1,16 @@
<template>
<div class="flex gap-2 items-center">
<IconButton
v-if="hasActiveLoadingBars && !hasVisibleActiveDownloadToasts"
v-tooltip="formatMessage(messages.viewActiveDownloads)"
type="quiet"
color="brand"
:label="formatMessage(messages.viewActiveDownloads)"
@click="openDownloadToast()"
>
<DownloadIcon />
</IconButton>
<div v-if="downloadState.total > 0 || hasActiveLoadingBars" class="relative">
<IconButton
v-tooltip="downloadToggleLabel"
:color="downloadState.hidden > 0 ? 'brand' : undefined"
type="quiet"
:label="downloadToggleLabel"
@click="toggleDownloadNotifications"
>
<DownloadIcon />
</IconButton>
</div>
<div v-if="offline" class="flex items-center gap-1">
<UnplugIcon class="text-secondary" />
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
@@ -218,8 +219,35 @@ const messages = defineMessages({
id: 'app.action-bar.view-active-downloads',
defaultMessage: 'View active downloads',
},
hideDownloads: {
id: 'app.action-bar.hide-downloads',
defaultMessage: 'Hide active downloads',
},
showDownloads: {
id: 'app.action-bar.show-downloads',
defaultMessage: 'Show active downloads',
},
})
const downloadState = computed(() => popupNotificationManager.getDownloadState())
const downloadToggleLabel = computed(() =>
formatMessage(
downloadState.value.hidden > 0
? messages.showDownloads
: downloadState.value.total > 0
? messages.hideDownloads
: messages.viewActiveDownloads,
),
)
function toggleDownloadNotifications(): void {
if (downloadState.value.total > 0) {
popupNotificationManager.toggleDownloadNotifications()
} else if (hasActiveLoadingBars.value) {
openDownloadToast()
}
}
const currentProcesses = ref<RunningProcess[]>([])
const selectedProcess = ref<RunningProcess | undefined>()
@@ -296,6 +324,7 @@ function goToTerminal(instanceId?: string) {
const currentLoadingBars = ref<LoadingBar[]>([])
const currentLoadingBarIconUrls = ref<Record<string, string | null>>({})
const notificationId = ref<string | number | null>(null)
const terminalNotificationIds = new Map<string, string | number>()
const dismissed = ref(false)
function getLoadingBarKey(loadingBar: LoadingBar): string {
@@ -341,6 +370,44 @@ function removeNotification(): void {
notificationId.value = null
}
function syncTerminalNotifications(): void {
const terminalNotifications = installJobNotifications.terminalNotifications.value
const currentJobIds = new Set(terminalNotifications.map((notification) => notification.id))
for (const terminal of terminalNotifications) {
const popupId = terminalNotificationIds.get(terminal.id)
let notification = popupId
? popupNotificationManager.getNotifications().find((candidate) => candidate.id === popupId)
: undefined
if (!notification) {
notification = popupNotificationManager.addPopupNotification({
title: terminal.title,
text: terminal.text,
type: terminal.type,
buttons: terminal.buttons,
onDismiss: terminal.onDismiss,
autoCloseMs: null,
})
terminalNotificationIds.set(terminal.id, notification.id)
continue
}
notification.title = terminal.title
notification.text = terminal.text
notification.type = terminal.type
notification.buttons = terminal.buttons
notification.onDismiss = terminal.onDismiss
}
for (const [jobId, popupId] of terminalNotificationIds) {
if (!currentJobIds.has(jobId)) {
popupNotificationManager.removeNotification(popupId)
terminalNotificationIds.delete(jobId)
}
}
}
function buildDownloadItems(): PopupNotificationProgressItem[] {
return [
...installJobNotifications.progressItems.value,
@@ -358,12 +425,13 @@ function buildDownloadItems(): PopupNotificationProgressItem[] {
]
}
const hasVisibleActiveDownloadToasts = computed(() => !!getNotification())
const hasActiveLoadingBars = computed(
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
)
function updateNotification(resummon = false): void {
syncTerminalNotifications()
if (resummon) {
dismissed.value = false
}
@@ -392,7 +460,6 @@ function updateNotification(resummon = false): void {
: formatMessage(messages.downloads)
notif.text = undefined
notif.progressItems = progressItems
notif.buttons = installJobNotifications.buttons.value
notif.progress = undefined
notif.waiting = undefined
} else {
@@ -403,7 +470,6 @@ function updateNotification(resummon = false): void {
type: 'download',
autoCloseMs: null,
progressItems,
buttons: installJobNotifications.buttons.value,
})
notificationId.value = notif.id
}
@@ -502,6 +568,8 @@ function selectProcess(process: RunningProcess) {
onBeforeUnmount(() => {
removeNotification()
terminalNotificationIds.forEach((id) => popupNotificationManager.removeNotification(id))
terminalNotificationIds.clear()
dismissed.value = false
window.removeEventListener('offline', handleOffline)
window.removeEventListener('online', handleOnline)
@@ -0,0 +1,269 @@
<template>
<Teleport to="body">
<div
v-if="activeItem"
class="expanded-image-modal"
role="dialog"
aria-modal="true"
:aria-label="activeItem.title || activeItem.alt"
@click="hide"
>
<div class="content">
<img
class="image"
:class="{ 'zoomed-in': zoomedIn }"
:src="activeItem.src"
:alt="activeItem.alt"
@click.stop
/>
<div class="floating" @click.stop>
<div v-if="activeItem.title || activeItem.description" class="text">
<h2 v-if="activeItem.title">{{ activeItem.title }}</h2>
<p v-if="activeItem.description">{{ activeItem.description }}</p>
</div>
<div class="controls">
<div class="buttons">
<IconButton label="Close" class="close" @click="hide">
<XIcon aria-hidden="true" />
</IconButton>
<slot name="actions" :item="activeItem" :index="activeIndex" :hide="hide" />
<IconButton label="Toggle zoom" @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" />
</IconButton>
<IconButton
v-if="items.length > 1"
label="Previous image"
class="previous"
@click="previous"
>
<LeftArrowIcon aria-hidden="true" />
</IconButton>
<IconButton
v-if="items.length > 1"
label="Next image"
class="next"
@click="next"
>
<RightArrowIcon aria-hidden="true" />
</IconButton>
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import {
ContractIcon,
ExpandIcon,
LeftArrowIcon,
RightArrowIcon,
XIcon,
} from '@modrinth/assets'
import { IconButton } from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { release_ads_window_hold, take_ads_window_hold } from '@/helpers/ads.js'
type ImagePreviewItem = {
id: string
src: string
alt: string
title?: string
description?: string
}
const props = defineProps<{
items: ImagePreviewItem[]
}>()
const emit = defineEmits<{
show: [item: ImagePreviewItem, index: number]
hide: []
navigate: [item: ImagePreviewItem, index: number, direction: 'next' | 'previous']
}>()
const activeId = ref<string | null>(null)
const activeIndex = computed(() => props.items.findIndex((item) => item.id === activeId.value))
const activeItem = computed(() => props.items[activeIndex.value] ?? null)
const zoomedIn = ref(false)
let adsWindowHold = false
function show(index: number) {
const item = props.items[index]
if (!item) return
if (!adsWindowHold) {
adsWindowHold = true
take_ads_window_hold()
}
activeId.value = item.id
zoomedIn.value = false
emit('show', item, index)
}
function hide() {
if (activeId.value === null) return
activeId.value = null
zoomedIn.value = false
if (adsWindowHold) {
adsWindowHold = false
release_ads_window_hold()
}
emit('hide')
}
function navigate(offset: number, direction: 'next' | 'previous') {
if (props.items.length < 2) return
const index = (activeIndex.value + offset + props.items.length) % props.items.length
activeId.value = props.items[index].id
zoomedIn.value = false
emit('navigate', props.items[index], index, direction)
}
function next() {
navigate(1, 'next')
}
function previous() {
navigate(-1, 'previous')
}
function keyListener(event: KeyboardEvent) {
if (!activeItem.value) return
if (document.querySelector('.modal-root')) return
if (event.key === 'Escape') {
event.preventDefault()
hide()
} else if (event.key === 'ArrowLeft') {
event.preventDefault()
previous()
} else if (event.key === 'ArrowRight') {
event.preventDefault()
next()
}
}
onMounted(() => document.addEventListener('keydown', keyListener))
onUnmounted(() => {
document.removeEventListener('keydown', keyListener)
if (adsWindowHold) release_ads_window_hold()
})
watch(activeItem, (item, previousItem) => {
if (!item && previousItem) hide()
})
defineExpose({ show, hide, next, previous })
</script>
<style scoped lang="scss">
.expanded-image-modal {
position: fixed;
z-index: 110;
overflow: auto;
inset: 0;
width: 100%;
height: 100%;
background-color: rgb(0 0 0 / 70%);
display: flex;
justify-content: center;
align-items: center;
.content {
--controls-safe-area: 5rem;
position: relative;
width: calc(100% - 2 * var(--gap-lg));
height: calc(100% - 2 * var(--gap-lg));
.image {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-width: 100%;
max-height: 100%;
border-radius: var(--radius-lg);
&.zoomed-in {
object-fit: cover;
top: calc((100% - var(--controls-safe-area)) / 2);
width: auto;
height: calc(100% - var(--controls-safe-area));
max-width: 100%;
}
}
.floating {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: var(--gap-md);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--gap-md);
transition: opacity 0.25s ease-in-out;
opacity: 1;
padding: 2rem 2rem 0;
&:not(&:hover) {
opacity: 0.4;
.text {
transform: translateY(2.5rem) scale(0.8);
opacity: 0;
}
.controls {
transform: translateY(0.25rem) scale(0.9);
}
}
.text {
display: flex;
flex-direction: column;
max-width: 40rem;
transition:
opacity 0.25s ease-in-out,
transform 0.25s ease-in-out;
text-shadow: 1px 1px 10px #000000d4;
margin-bottom: 0.25rem;
gap: 0.5rem;
h2 {
color: var(--dark-color-base);
font-size: 1.25rem;
text-align: center;
margin: 0;
}
p {
color: var(--dark-color-base);
margin: 0;
}
}
.controls {
background-color: var(--color-raised-bg);
padding: var(--gap-md);
border-radius: var(--radius-md);
transition:
opacity 0.25s ease-in-out,
transform 0.25s ease-in-out;
}
}
}
}
.buttons {
display: flex;
gap: 0.5rem;
}
</style>
@@ -3,12 +3,14 @@ import { NotepadTextIcon, XIcon } from '@modrinth/assets'
import { Button, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { type } from '@tauri-apps/plugin-os'
import { $fetch } from 'ofetch'
import { onMounted, ref } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { release_ads_window_hold, take_ads_window_hold } from '@/helpers/ads.js'
import { list } from '@/helpers/instance'
import { get as getCreds } from '@/helpers/mr_auth.ts'
let adsWindowHold = false
type Survey = {
id: string
tally_id: string
@@ -92,30 +94,44 @@ async function openSurvey() {
onOpen: () => console.info('Opened user survey'),
onClose: () => {
console.info('Closed user survey')
show_ads_window()
if (adsWindowHold) {
adsWindowHold = false
release_ads_window_hold()
}
},
onSubmit: () => console.info('Active user survey submitted'),
}
try {
hide_ads_window()
await take_ads_window_hold()
adsWindowHold = true
if (tallyWindow.Tally?.openPopup) {
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
dismissSurvey()
tallyWindow.Tally.openPopup(formId, popupOptions)
} else {
console.warn('Tally script not yet loaded')
show_ads_window()
adsWindowHold = false
await release_ads_window_hold()
}
} catch (e) {
console.error('Error opening Tally popup:', e)
show_ads_window()
if (adsWindowHold) {
adsWindowHold = false
await release_ads_window_hold()
}
}
console.info(`Found user survey to show with tally_id: ${formId}`)
tallyWindow.Tally?.openPopup(formId, popupOptions)
}
onUnmounted(() => {
if (adsWindowHold) {
adsWindowHold = false
release_ads_window_hold()
}
})
function dismissSurvey() {
if (!availableSurvey.value) return
localStorage.setItem(`survey-${availableSurvey.value.id}-display`, String(new Date()))
@@ -23,9 +23,8 @@
</IconButton>
<IconButton
type="quiet"
color="red"
label="Close window"
class="relative expanded-button close-button hover:!bg-red focus-visible:!bg-red"
class="relative expanded-button close-button"
@click="handleClose"
>
<XIcon />
@@ -10,7 +10,6 @@ import {
import { Admonition, Button, ButtonLink, Collapsible, IconButton, NewModal } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { handleSevereError } from '@/store/error.js'
@@ -29,19 +28,13 @@ function show(errorVal: { message?: string }) {
matchedError.value = findMinecraftAuthError(rawError.value)
debugCollapsed.value = true
hide_ads_window()
modal.value?.show()
}
function hide() {
onModalHide()
modal.value?.hide()
}
function onModalHide() {
show_ads_window()
}
defineExpose({
show,
hide,
@@ -74,7 +67,7 @@ async function copyToClipboard(text: string) {
</script>
<template>
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'" @hide="onModalHide">
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'">
<div class="flex flex-col gap-6">
<Admonition
type="warning"
@@ -20,18 +20,12 @@
</div>
<div class="flex flex-col gap-6 px-6 pb-6">
<div class="flex justify-end gap-2">
<ButtonLink class="w-full" href="https://support.modrinth.com" @click="modal?.hide()">
<div class="grid grid-cols-2 gap-2">
<ButtonLink href="https://support.modrinth.com" @click="modal?.hide()">
<MessagesSquareIcon />
{{ formatMessage(messages.getSupport) }}
</ButtonLink>
<Button
type="colored"
color="brand"
class="w-full"
:disabled="loadingSignIn"
@click="signIn"
>
<Button type="colored" color="brand" :disabled="loadingSignIn" @click="signIn">
<SpinnerIcon v-if="loadingSignIn" class="animate-spin" />
<svg
v-else
@@ -3,7 +3,6 @@
ref="modal"
:header="formatMessage(messages.installToPlay)"
:closable="true"
:on-hide="show_ads_window"
max-width="544px"
width="544px"
>
@@ -169,7 +168,6 @@ import {
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, nextTick, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
import { get_project, get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import { injectServerInstall } from '@/providers/server-install'
@@ -352,7 +350,6 @@ async function show(
if (modpackVersionIdVal) await fetchData(modpackVersionIdVal)
hide_ads_window()
modal.value?.show(e)
await nextTick()
forceCheckTableScroll()
@@ -403,8 +400,7 @@ const messages = defineMessages({
},
reviewedFiles: {
id: 'app.modal.install-to-play.reviewed-files',
defaultMessage:
'A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack).',
defaultMessage: "Files that aren't published to Modrinth aren't reviewed.",
},
installAnyway: {
id: 'app.modal.install-to-play.install-anyway',
@@ -268,7 +268,6 @@ import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, nextTick, ref } from 'vue'
import { config } from '@/config'
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
import { toError } from '@/helpers/errors'
import type { SharedInstanceInstallPreview } from '@/helpers/install'
import { create_report } from '@/helpers/reports'
@@ -448,7 +447,6 @@ function handleCancel() {
function handleHide() {
resetReportState()
creator.value = null
show_ads_window()
}
function resetReportState() {
reportMode.value = false
@@ -488,7 +486,6 @@ function showReport(
}
function showPreview(previewValue: SharedInstanceInstallPreview, event?: MouseEvent) {
preview.value = previewValue
hide_ads_window()
modal.value?.show(event)
void nextTick(() => forceCheckTableScroll())
}
@@ -598,8 +595,7 @@ const messages = defineMessages({
},
reviewedFiles: {
id: 'app.modal.install-to-play.reviewed-files',
defaultMessage:
'A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack).',
defaultMessage: "Files that aren't published to Modrinth aren't reviewed.",
},
installAnyway: {
id: 'app.modal.install-to-play.install-anyway',
@@ -404,8 +404,6 @@ export async function useInstallJobNotifications(opts: {
}
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))
@@ -526,8 +524,12 @@ export async function useInstallJobNotifications(opts: {
)
}
const activeJobs = computed(() =>
jobs.value.filter((job) => job.status === 'queued' || job.status === 'running'),
)
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
jobs.value.map((job) => {
activeJobs.value.map((job) => {
const progress = getEffectiveProgress(job)
return {
@@ -536,20 +538,26 @@ export async function useInstallJobNotifications(opts: {
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,
waiting: !job.progress && job.status === 'running',
showProgress: job.status === 'running',
progressType: getProgressType(job),
progressCurrent: progress?.current,
progressTotal: progress?.total,
buttons: getButtons(job),
dismissible: isTerminalJob(job),
onDismiss: getDismissHandler(job),
}
}),
)
const buttons = computed<PopupNotificationButton[] | undefined>(() => undefined)
const terminalNotifications = computed(() =>
jobs.value.filter(isTerminalJob).map((job) => ({
id: job.job_id,
title: getTitle(job),
text: getText(job),
type: job.status === 'failed' ? ('error' as const) : ('warning' as const),
buttons: getButtons(job),
onDismiss: getDismissHandler(job),
})),
)
async function refreshMetadata(notify = true) {
const request = ++metadataRequest
@@ -631,10 +639,10 @@ export async function useInstallJobNotifications(opts: {
await refresh(false)
return {
active: computed(() => jobs.value.length > 0),
active: computed(() => activeJobs.value.length > 0),
title: computed(() => formatMessage(messages.installs)),
progressItems,
buttons,
terminalNotifications,
refresh,
dispose: () => {
for (const timeout of copiedResetTimeouts.values()) {
+21 -2
View File
@@ -8,8 +8,27 @@ export async function init_ads_window(overrideShown = false) {
})
}
export async function show_ads_window() {
return await invoke('plugin:ads|show_ads_window', { dpr: window.devicePixelRatio })
let adsWindowHoldUpdate = Promise.resolve()
async function update_ads_window_hold(acquire) {
adsWindowHoldUpdate = adsWindowHoldUpdate
.catch(() => {})
.then(() =>
invoke('plugin:ads|update_ads_window_hold', {
acquire,
dpr: window.devicePixelRatio,
}),
)
return await adsWindowHoldUpdate
}
export async function take_ads_window_hold() {
return await update_ads_window_hold(true)
}
export async function release_ads_window_hold() {
return await update_ads_window_hold(false)
}
export async function hide_ads_window(reset) {
+35
View File
@@ -77,6 +77,10 @@ export async function get_content_items(
return await invoke('plugin:instance|instance_get_content_items', { instanceId, cacheBehaviour })
}
export async function refresh_content_updates(instanceId: string): Promise<void> {
return await invoke('plugin:instance|instance_refresh_content_updates', { instanceId })
}
// Linked modpack info returned from backend
export interface LinkedModpackInfo {
project: Labrinth.Projects.v2.Project
@@ -132,6 +136,37 @@ export async function get_mod_full_path(instanceId: string, projectPath: string)
return await invoke('plugin:instance|instance_get_mod_full_path', { instanceId, projectPath })
}
export interface InstanceScreenshot {
file_name: string
created_at: string
path: string
url: string
}
export async function list_screenshots(instanceId: string): Promise<InstanceScreenshot[]> {
return await invoke('plugin:instance|instance_list_screenshots', { instanceId })
}
export async function delete_screenshot(instanceId: string, fileName: string): Promise<void> {
return await invoke('plugin:instance|instance_delete_screenshot', { instanceId, fileName })
}
export async function export_screenshots(
instanceId: string,
fileNames: string[],
exportPath: string,
): Promise<void> {
return await invoke('plugin:instance|instance_export_screenshots', {
instanceId,
fileNames,
exportPath,
})
}
export async function open_screenshot(instanceId: string, fileName: string): Promise<void> {
return await invoke('plugin:instance|instance_open_screenshot', { instanceId, fileName })
}
export interface JavaVersion {
parsed_version: number
version: string
@@ -14,6 +14,9 @@
"app.action-bar.install.copied-details": {
"message": "Zkopírováno"
},
"app.action-bar.install.copy-details": {
"message": "Kopírovat detaily"
},
"app.action-bar.install.dismiss": {
"message": "Zavřít"
},
@@ -26,6 +29,9 @@
"app.action-bar.install.summary.canceled": {
"message": "Zrušeno"
},
"app.action-bar.install.summary.cleanup-incomplete": {
"message": "Čištění nebylo dokončeno"
},
"app.action-bar.install.unknown-instance": {
"message": "Neznámá instance"
},
@@ -65,6 +71,9 @@
"app.action-bar.view-logs": {
"message": "Zobrazit logy"
},
"app.ads-consent.title": {
"message": "Vaše soukromí a způsob, jakým reklamy podporují Modrinth"
},
"app.appearance-settings.advanced-rendering.title": {
"message": "Pokročilé vykreslování"
},
@@ -212,6 +221,9 @@
"app.install.phase.running_loader_processors": {
"message": "Spuštění loader procesů"
},
"app.instance.admonitions.shared-instance.removed-label": {
"message": "Odstraněno"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Všechna data tvé instance budou trvale smazána, včetně světů, konfigurací a veškerého nainstalovaného obsahu."
},
@@ -179,6 +179,9 @@
"app.auth-servers.unreachable.header": {
"message": "Kan ikke nå autentificeringsservere"
},
"app.behavior-settings.content.title": {
"message": "Hjem og indhold"
},
"app.browse.add-servers-to-instance": {
"message": "Tilføjet server til instance"
},
@@ -200,12 +203,18 @@
"app.browse.back-to-instance": {
"message": "Tilbage til instance"
},
"app.browse.discover-project-type": {
"message": "Udforsk {projectType}"
},
"app.browse.discover-servers": {
"message": "Opdag servere"
},
"app.browse.hide-added-servers": {
"message": "Gem servere som allerede er tilføjet"
},
"app.browse.hide-installed-modpacks": {
"message": "Skjul allerede installeret"
},
"app.browse.project-type.modpacks": {
"message": "Modpacks"
},
@@ -245,15 +254,30 @@
"app.install.phase.downloading_minecraft": {
"message": "Downloader Minecraft"
},
"app.install.phase.finalizing": {
"message": "Færdiggører"
},
"app.install.phase.preparing_instance": {
"message": "I kø til at intallere"
},
"app.install.phase.preparing_java": {
"message": "Forbereder Java"
},
"app.install.phase.preparing_java.downloading": {
"message": "Downloader Java {version}"
},
"app.install.phase.preparing_java.extracting": {
"message": "Udpakker Java {version}"
},
"app.install.phase.preparing_java.fetching-metadata": {
"message": "Henter Java {version}"
},
"app.install.phase.preparing_java.resolving": {
"message": "Forbereder Java {version}"
},
"app.install.phase.preparing_java.validating": {
"message": "Validerer Java {version}"
},
"app.instance.admonitions.shared-instance.added-label": {
"message": "Tilføjet"
},
@@ -359,6 +383,12 @@
"app.instance.share.remove-user-modal.effects-label": {
"message": "Hvad vil der ske?"
},
"app.instance.share.remove-user-modal.header": {
"message": "Fjern adgang"
},
"app.instance.share.remove-user-modal.remove-button": {
"message": "Fjern adgang"
},
"app.instance.share.remove-user-modal.user-avatar-alt": {
"message": "{username}'s avatar"
},
+33 -6
View File
@@ -210,7 +210,7 @@
"message": "Rechte Seitenleiste ausblenden"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "Sicherheitswarnung vor der Installation eines Modrinth-Packs (.mrpack) anzeigen, das nicht auf Modrinth gehostet wird."
"message": "Zeige eine Sicherheitswarnung an, bevor ein Modrinth-Paket (.mrpack) installiert wird, welches nicht auf Modrinth gehostet wird."
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "Warne mich, bevor unbekannte Modpacks installiert werden"
@@ -590,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.refreshing": {
"message": "Wird aktualisiert..."
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server entfernen"
},
@@ -849,7 +852,7 @@
"message": "Startet Instanzen im Vollbildmodus durch Aktualisieren ihrer options.txt-Datei."
},
"app.settings.default-instance-options.fullscreen.title": {
"message": "Vollbildschirm"
"message": "Vollbild"
},
"app.settings.default-instance-options.height.description": {
"message": "Die Höhe des Spielfensters beim Starten"
@@ -981,7 +984,7 @@
"message": "Neues App-Verzeichnis auswählen"
},
"app.settings.resource-management.app-directory.title": {
"message": "App Installation"
"message": "App-Verzeichnis"
},
"app.settings.resource-management.maximum-concurrent-downloads.description": {
"message": "Anzahl der Dateien, die die App auf einmal herunterladen kann. Senke dies, wenn downloads auf deiner Verbindung unzuverlässig sind. Benötigt einen App-Neustart."
@@ -1005,7 +1008,7 @@
"message": "Verhalten"
},
"app.settings.tabs.default-instance-options": {
"message": "Standard Spieleinstellungen"
"message": "Standard-Spieloptionen"
},
"app.settings.tabs.java-installations": {
"message": "Java Installationen"
@@ -1188,10 +1191,10 @@
"message": "Das Feedback geht direkt an das Modrinth-Team und wird helfen, zukünftige Updates zu gestalten!"
},
"app.survey.no-thanks": {
"message": "Nein, danke"
"message": "Nein danke"
},
"app.survey.take-survey": {
"message": "Beantworten"
"message": "An Umfrage teilnehmen"
},
"app.survey.title": {
"message": "Hey, Modrinth Nutzer!"
@@ -1226,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Version {version} wurde erfolgreich installiert!"
},
"app.user.project.install-to-instance": {
"message": "In Instanz installieren"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1619,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Spielstart Hooks"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Hooks werden im Arbeitsverzeichnis der Instanz mit den folgenden Variablen ausgeführt:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: Der absolute Pfad zum Ordner der Instanz"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: Der Name des Ordners der Instanz"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: Die JVM-Argumente, die dem Spiel zur Verfügung gestellt werden"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: Der absolute Pfad zur Java-Binärdatei"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: Ein Alias für $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: Der Name der Instanz"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
+33 -6
View File
@@ -210,7 +210,7 @@
"message": "Rechte Seitenleiste ausblenden"
},
"app.appearance-settings.unknown-pack-warning.description": {
"message": "Sicherheitswarnung vor der Installation eines Modrinth-Packs (.mrpack) anzeigen, das nicht auf Modrinth gehostet wird."
"message": "Zeige eine Sicherheitswarnung an, bevor ein Modrinth-Paket (.mrpack) installiert wird, welches nicht auf Modrinth gehostet wird."
},
"app.appearance-settings.unknown-pack-warning.title": {
"message": "Warne mich, bevor unbekannte Modpacks installiert werden"
@@ -590,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Keine Server oder Welten hinzugefügt"
},
"app.instance.worlds.refreshing": {
"message": "Wird aktualisiert..."
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server entfernen"
},
@@ -849,7 +852,7 @@
"message": "Startet Instanzen im Vollbildmodus durch Aktualisieren ihrer options.txt-Datei."
},
"app.settings.default-instance-options.fullscreen.title": {
"message": "Vollbildschirm"
"message": "Vollbild"
},
"app.settings.default-instance-options.height.description": {
"message": "Die Höhe des Spielfensters beim Starten"
@@ -981,7 +984,7 @@
"message": "Neues App-Verzeichnis auswählen"
},
"app.settings.resource-management.app-directory.title": {
"message": "App Installation"
"message": "App-Verzeichnis"
},
"app.settings.resource-management.maximum-concurrent-downloads.description": {
"message": "Anzahl der Dateien, die die App auf einmal herunterladen kann. Senke dies, wenn downloads auf deiner Verbindung unzuverlässig sind. Benötigt einen App-Neustart."
@@ -1005,7 +1008,7 @@
"message": "Verhalten"
},
"app.settings.tabs.default-instance-options": {
"message": "Standard Spieleinstellungen"
"message": "Standard-Spieloptionen"
},
"app.settings.tabs.java-installations": {
"message": "Java-Installationen"
@@ -1188,10 +1191,10 @@
"message": "Das Feedback geht direkt an das Modrinth-Team und wird helfen, zukünftige Updates zu gestalten!"
},
"app.survey.no-thanks": {
"message": "Nein, danke"
"message": "Nein danke"
},
"app.survey.take-survey": {
"message": "Beantworten"
"message": "An Umfrage teilnehmen"
},
"app.survey.title": {
"message": "Hey, Modrinth Nutzer!"
@@ -1226,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Version {version} wurde erfolgreich installiert!"
},
"app.user.project.install-to-instance": {
"message": "In Instanz installieren"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1619,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Start-Hooks"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Hooks werden im Arbeitsverzeichnis der Instanz mit den folgenden Variablen ausgeführt:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: Der absolute Pfad zum Ordner der Instanz"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: Der Name des Ordners der Instanz"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: Die JVM-Argumente, die dem Spiel zur Verfügung gestellt werden"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: Der absolute Pfad zur Java-Binärdatei"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: Ein Alias für $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: Der Name der Instanz"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
@@ -8,6 +8,9 @@
"app.action-bar.downloads": {
"message": "Downloads"
},
"app.action-bar.hide-downloads": {
"message": "Hide active downloads"
},
"app.action-bar.hide-more-running-instances": {
"message": "Hide more running instances"
},
@@ -110,6 +113,9 @@
"app.action-bar.reload-to-update": {
"message": "Reload to update"
},
"app.action-bar.show-downloads": {
"message": "Show active downloads"
},
"app.action-bar.show-more-running-instances": {
"message": "Show more running instances"
},
@@ -723,7 +729,7 @@
"message": "For support requests, contact our <support-link>support team</support-link>. For bug reports, open a <github-link>GitHub issue</github-link>."
},
"app.modal.install-to-play.reviewed-files": {
"message": "A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack)."
"message": "Files that aren't published to Modrinth aren't reviewed."
},
"app.modal.install-to-play.shared-instance": {
"message": "Shared instance"
+429 -15
View File
@@ -174,7 +174,7 @@
"message": "Ocultar etiqueta de nombre"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "Muestra los mundos recientes en la sección \"Volver a jugar\" en la página principal."
"message": "Muestra los mundos recientes en la sección \"Volver a jugar\" en la página de inicio."
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "Volver a jugar mundos"
@@ -186,7 +186,7 @@
"message": "Minimizar app"
},
"app.appearance-settings.native-decorations.description": {
"message": "Usa el borde de ventana de tu sistema. Requiere reiniciar la aplicación."
"message": "Usa el borde de ventana de tu sistema. Necesita reiniciar la aplicación."
},
"app.appearance-settings.native-decorations.title": {
"message": "Decoraciones nativas"
@@ -225,7 +225,7 @@
"message": "Confirmaciones"
},
"app.behavior-settings.content.title": {
"message": "Página principal y contenido"
"message": "Página de inicio y contenido"
},
"app.behavior-settings.startup-and-navigation.title": {
"message": "Inicio y navegación"
@@ -258,7 +258,7 @@
"message": "Descubrir servidores"
},
"app.browse.hide-added-servers": {
"message": "Ocultar los servidores ya agregados"
"message": "Ocultar los servidores ya añadidos"
},
"app.browse.hide-installed-modpacks": {
"message": "Esconder los ya instalados"
@@ -372,7 +372,7 @@
"message": "Enviar actualización"
},
"app.instance.admonitions.shared-instance.review-description": {
"message": "Revisar los cambios de contenido que se compartirán con los usuarios de esta instancia."
"message": "Revisa los cambios de contenido que se compartirán con los usuarios de esta instancia."
},
"app.instance.admonitions.shared-instance.review-header": {
"message": "Revisar cambios"
@@ -384,7 +384,7 @@
"message": "Actualizando..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Se requiere una actualización para jugar a {name}. por favor actualiza a la versión más reciente para abrir el juego."
"message": "Se necesita una actualización para jugar a {name}. Por favor actualiza a la versión más reciente para iniciar el juego."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Hay una actualización disponible"
@@ -483,7 +483,7 @@
"message": "Ningún usuario coincide con tus filtros."
},
"app.instance.share.remove-user-modal.effect-access": {
"message": "Ya no recibirán actualizaciones para esta instancia compartida"
"message": "Ya no recibirán actualizaciones para esta instancia"
},
"app.instance.share.remove-user-modal.effect-installed-copy": {
"message": "Cualquier copia que tengan instalada se quedará en su dispositivo"
@@ -510,10 +510,10 @@
"message": "Si revocas el acceso de {username} a esta instancia, tendrás que invitarlos otra vez para que sigan recibiendo actualizaciones."
},
"app.instance.share.sign-in.button": {
"message": "Iniciar sesión"
"message": "Inicia sesión"
},
"app.instance.share.unable-to-connect.description": {
"message": "El servicio de instancias compartidas no está disponible, inténtalo otra vez más tarde"
"message": "El servicio de instancias compartidas no está disponible, por favor inténtalo otra vez más tarde"
},
"app.instance.share.unable-to-connect.heading": {
"message": "No se puede conectar"
@@ -590,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "No hay servidores ni mundos añadidos"
},
"app.instance.worlds.refreshing": {
"message": "Recargando..."
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Eliminar servidor"
},
@@ -717,7 +720,7 @@
"message": "Reporte enviado"
},
"app.modal.install-to-play.report-support-and-bugs": {
"message": "Para solicitudes de ayuda, contacte a nuestro <support-link>equipo de soporte</support-link>. Para reportar un bug, abra una <github-link>incidencia (issue) en GitHub</github-link>."
"message": "Para solicitudes de ayuda, contacte a nuestro <support-link>equipo de soporte</support-link>. Para reportar un bug, abra una <github-link>issue en GitHub</github-link>."
},
"app.modal.install-to-play.reviewed-files": {
"message": "Un archivo solo se revisa si se publica en Modrinth, sin importar su formato (incluido el .mrpack)."
@@ -728,6 +731,9 @@
"app.modal.install-to-play.shared-instance-content": {
"message": "Contenido de la instancia compartida"
},
"app.modal.install-to-play.shared-instance-unknown-files-description": {
"message": "Esta instancia contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
},
"app.modal.install-to-play.unknown-files-description": {
"message": "Este modpack para servidor contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
},
@@ -737,6 +743,9 @@
"app.modal.install-to-play.unrecognized-files": {
"message": "Archivos no reconocidos"
},
"app.modal.install-to-play.user-blocked": {
"message": "Usuario bloqueado"
},
"app.modal.install-to-play.view-contents": {
"message": "Ver contenidos"
},
@@ -744,11 +753,56 @@
"message": "Actualizar para jugar"
},
"app.modal.update-to-play.removed": {
"message": "Eliminado"
"message": "Se eliminó"
},
"app.modal.update-to-play.server-modpack-unknown-files-description": {
"message": "Esta actualización de modpack para servidor contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
},
"app.modal.update-to-play.shared-instance-added-label": {
"message": "Se añadió"
},
"app.modal.update-to-play.shared-instance-removed-label": {
"message": "Se eliminó"
},
"app.modal.update-to-play.shared-instance-unknown-files-description": {
"message": "Esta actualización de instancia contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
},
"app.modal.update-to-play.update-required": {
"message": "Es necesario actualizar"
},
"app.modal.update-to-play.update-required-description": {
"message": "Se necesita una actualización para jugar en {name}. Por favor actualiza a a la versión más reciente para iniciar el juego."
},
"app.nav.create-new-instance": {
"message": "Crear una instancia nueva"
},
"app.nav.home": {
"message": "Inicio"
},
"app.nav.library": {
"message": "Biblioteca"
},
"app.nav.modrinth-account": {
"message": "Cuenta Modrinth"
},
"app.nav.modrinth-hosting": {
"message": "Modrinth Hosting"
},
"app.nav.sign-in-to-modrinth-account": {
"message": "Inicia sesión con una cuenta Modrinth"
},
"app.nav.signed-in-as": {
"message": "Sesión iniciada como <user>{username}</user>"
},
"app.nav.upgrade-to-modrinth-plus": {
"message": "Mejorar a Modrinth+"
},
"app.news.title": {
"message": "Noticias"
},
"app.news.view-all": {
"message": "Ver todas las noticias"
},
"app.project.install-button.already-installed": {
"message": "Este proyecto ya está instalado"
},
@@ -758,6 +812,9 @@
"app.project.install-context.back-to-browse": {
"message": "Volver al explorador"
},
"app.project.install-context.back-to-instance": {
"message": "Volver a la instancia"
},
"app.project.version.all-versions": {
"message": "Todas las versiones"
},
@@ -770,21 +827,177 @@
"app.project.versions.already-installed": {
"message": "Ya instalado"
},
"app.quick-instance-switcher.drag-show-tooltip": {
"message": "Arrastra para mostrar las instancias recientes"
},
"app.quick-instance-switcher.drag-tooltip": {
"message": "Arrastra para cambiar el tamaño"
},
"app.restarting": {
"message": "Reiniciando..."
},
"app.settings.app-version": {
"message": "App de Modrinth {version}"
},
"app.settings.default-instance-options.environment-variables.description": {
"message": "Variables de entorno puestas al iniciar una instancia."
},
"app.settings.default-instance-options.environment-variables.placeholder": {
"message": "Ingresa variables de entorno..."
},
"app.settings.default-instance-options.environment-variables.title": {
"message": "Variables de entorno"
},
"app.settings.default-instance-options.fullscreen.description": {
"message": "Inicia instancias en pantalla completa al modificar su archivo options.txt."
},
"app.settings.default-instance-options.fullscreen.title": {
"message": "Pantalla completa"
},
"app.settings.default-instance-options.height.description": {
"message": "El largo que tendrá la ventana del juego al iniciar."
},
"app.settings.default-instance-options.height.placeholder": {
"message": "Ingresa la altura..."
},
"app.settings.default-instance-options.height.title": {
"message": "Largo"
},
"app.settings.default-instance-options.java-arguments.description": {
"message": "Los argumentos dados a Java al iniciar una instancia."
},
"app.settings.default-instance-options.java-arguments.placeholder": {
"message": "Ingresa los argumentos de Java..."
},
"app.settings.default-instance-options.java-arguments.title": {
"message": "Argumentos de Java"
},
"app.settings.default-instance-options.memory-allocation.description": {
"message": "La memoria máxima disponible para cada instancia."
},
"app.settings.default-instance-options.memory-allocation.title": {
"message": "Asignación de memoria"
},
"app.settings.default-instance-options.post-exit-hook.description": {
"message": "Se ejecutan tras cerrar el juego."
},
"app.settings.default-instance-options.post-exit-hook.placeholder": {
"message": "Ingresa comandos post-cierre..."
},
"app.settings.default-instance-options.post-exit-hook.title": {
"message": "Comandos post-cierre"
},
"app.settings.default-instance-options.pre-launch-hook.description": {
"message": "Se ejecutan antes de iniciar el juego."
},
"app.settings.default-instance-options.pre-launch-hook.placeholder": {
"message": "Ingresa comandos pre-inicio..."
},
"app.settings.default-instance-options.pre-launch-hook.title": {
"message": "Comandos pre-inicio"
},
"app.settings.default-instance-options.width.description": {
"message": "El ancho que tendrá la ventana del juego al iniciar."
},
"app.settings.default-instance-options.width.placeholder": {
"message": "Ingresa el ancho..."
},
"app.settings.default-instance-options.width.title": {
"message": "Ancho"
},
"app.settings.default-instance-options.wrapper-hook.description": {
"message": "Envuelven al proceso iniciador de Minecraft para añadir funcionalidades o configuraciones."
},
"app.settings.default-instance-options.wrapper-hook.placeholder": {
"message": "Ingresa comandos de envoltura..."
},
"app.settings.default-instance-options.wrapper-hook.title": {
"message": "Comandos de envoltura"
},
"app.settings.developer-mode-button.label": {
"message": "Alternar el modo desarrollador"
},
"app.settings.developer-mode-enabled": {
"message": "Modo desarrollador activado."
},
"app.settings.downloading": {
"message": "Descargando v{version}"
},
"app.settings.java-installations.location.title": {
"message": "Directorio de Java {version, number}"
},
"app.settings.operating-system.macos": {
"message": "macOS"
},
"app.settings.privacy.ads-consent.intro": {
"message": "Los anuncios hacen posible a Modrinth y financian los pagos a los creadores. Nuestros socios pueden guardar o acceder a cookies en la aplicación para personalizar anuncios y medir su rendimiento. Puedes rechazar esto o administrar tus preferencias abajo."
},
"app.settings.privacy.discord-rich-presence.description": {
"message": "Mostrar la aplicación de Modrinth como tu actividad actual en Discord. Esto no afecta a cualquier Rich Presence añadida a instancias con mods. Necesita reiniciar la app."
},
"app.settings.privacy.discord-rich-presence.title": {
"message": "Discord Rich Presence"
},
"app.settings.privacy.telemetry.description": {
"message": "Modrinth recolecta datos analíticos anónimos y datos de uso para mejorar la experiencia de nuestros usuarios y para personalizar tu experiencia. Al desactivar esta opción, tus datos dejarán de ser recolectados."
},
"app.settings.privacy.telemetry.title": {
"message": "Telemetría"
},
"app.settings.resource-management.always-show-copy-details.description": {
"message": "Muestra la opción 'Copiar detalles' mientras haya una instalación en cola o instalando. Siempre está disponible para las instalaciones fallidas o interrumpidas."
},
"app.settings.resource-management.always-show-copy-details.title": {
"message": "Siempre mostrar 'Copiar detalles'"
},
"app.settings.resource-management.app-cache.confirm.description": {
"message": "La app cargará más lento hasta que se reconstruya completamente el caché."
},
"app.settings.resource-management.app-cache.confirm.title": {
"message": "¿Limpiar el caché?"
},
"app.settings.resource-management.app-cache.description": {
"message": "Borra todos los datos en caché y los redescarga de Modrinth. La app cargará más lento hasta que se reconstruya completamente el caché."
},
"app.settings.resource-management.app-cache.purge": {
"message": "Limpiar caché"
},
"app.settings.resource-management.app-cache.title": {
"message": "Caché de la aplicación"
},
"app.settings.resource-management.app-database-backups.description": {
"message": "Los respaldos de los datos de aplicación importantes se guardan aquí, en caso de que los necesites luego."
},
"app.settings.resource-management.app-database-backups.open-folder": {
"message": "Abrir la carpeta de respaldos"
},
"app.settings.resource-management.app-database-backups.title": {
"message": "Respaldos de la base de datos de la aplicación"
},
"app.settings.resource-management.app-directory.browse": {
"message": "Buscar un directorio para la aplicación"
},
"app.settings.resource-management.app-directory.description": {
"message": "Aquí es donde la aplicación guardará las instancias y otros archivos. Los cambios entrarán en efecto al reiniciar la app."
},
"app.settings.resource-management.app-directory.select": {
"message": "Seleccionar un directorio nuevo para la app"
},
"app.settings.resource-management.app-directory.title": {
"message": "Directorio de la aplicación"
},
"app.settings.resource-management.maximum-concurrent-downloads.description": {
"message": "El número de archivos que la app puede descargar a la vez. Baja este número si las descargas no son estables con tu conexión. Necesita reiniciar la app."
},
"app.settings.resource-management.maximum-concurrent-downloads.title": {
"message": "Máximas descargas a la vez"
},
"app.settings.resource-management.maximum-concurrent-writes.description": {
"message": "El número de archivos que la app puede escribir en el disco a la vez. Baja este número si frecuentemente encuentras errores de I/O. Necesita reiniciar la app."
},
"app.settings.resource-management.maximum-concurrent-writes.title": {
"message": "Máximas escrituras a la vez"
},
"app.settings.sidebar.label.instances": {
"message": "Instancias"
},
@@ -794,6 +1007,9 @@
"app.settings.tabs.behavior": {
"message": "Comportamiento"
},
"app.settings.tabs.default-instance-options": {
"message": "Opciones por defecto del juego"
},
"app.settings.tabs.java-installations": {
"message": "Instalaciones de Java"
},
@@ -968,11 +1184,17 @@
"app.skins.toggle-ears-features-on": {
"message": "Activar"
},
"app.survey.body": {
"message": "¿Te importaría responder unas cuantas preguntas sobre tu experiencia con la aplicación de Modrinth?"
},
"app.survey.footer": {
"message": "¡Tus comentarios llegarán directamente al equipo de Modrinth, y los ayudará a hacer mejores actualizaciones!"
},
"app.survey.no-thanks": {
"message": "No gracias"
"message": "No, gracias"
},
"app.survey.take-survey": {
"message": "Aceptar encuesta"
},
"app.survey.title": {
"message": "¡Hola, usuario de Modrinth!"
@@ -1007,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "¡La versión {version} se ha instalado correctamente!"
},
"app.user.project.install-to-instance": {
"message": "Instalar a instancia"
},
"app.world.server-modal.placeholder-address": {
"message": "ejemplo.modrinth.gg"
},
@@ -1025,18 +1250,39 @@
"app.world.world-item.players-online": {
"message": "{count} en línea"
},
"content.shared-instance.change-version-body": {
"message": "Cambiar la versión solo cambiará tu copia local. Las actualizaciones futuras a la instancia pueden restablecerla o cambiarla de nuevo."
},
"content.shared-instance.delete-bulk-body": {
"message": "Algunos proyectos seleccionados hacen parte de la instancia compartida. Borrarlos solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden restablecerlos o cambiarlos de nuevo."
},
"content.shared-instance.delete-button": {
"message": "Borrar de todas formas"
},
"content.shared-instance.delete-many-button": {
"message": "Borrar {count, number} proyectos de todas formas"
},
"content.shared-instance.delete-single-body": {
"message": "Borrarlo solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden restablecerlo o cambiarlo de nuevo."
},
"content.shared-instance.disable-bulk-body": {
"message": "Algunos proyectos seleccionados hacen parte de la instancia compartida. Desactivarlos solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden reactivarlos, restablecerlos o cambiarlos de nuevo."
},
"content.shared-instance.disable-button": {
"message": "Desactivar de todas formas"
},
"content.shared-instance.disable-many-button": {
"message": "Desactivar {count, number} proyectos de todas formas"
},
"content.shared-instance.disable-single-body": {
"message": "Desactivarlo solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden reactivarlo, restablecerlo o cambiarlo de nuevo."
},
"content.shared-instance.unlink-body": {
"message": "Desvincular solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden restablecerlo o cambiarlo de nuevo."
},
"content.shared-instance.warning-header": {
"message": "Esto es parte de la instancia compartida"
},
"friends.action.add-friend": {
"message": "Añadir un amigo"
},
@@ -1100,6 +1346,66 @@
"friends.sign-in-to-add-friends": {
"message": "<link>¡Inicia sesión en una cuenta de Modrinth</link> para añadir amigos y ver qué están jugando!"
},
"installation-settings.shared-instance.linked-title": {
"message": "Vincular instancia compartida"
},
"installation-settings.shared-instance.title": {
"message": "Despublicar instancia"
},
"installation-settings.shared-instance.unlink-button": {
"message": "Desvincular instancia compartida"
},
"installation-settings.shared-instance.unlink-description": {
"message": "Desconecta esta instancia local de cualquier actualización futura."
},
"installation-settings.shared-instance.unlinking-button": {
"message": "Desvinculando..."
},
"installation-settings.shared-instance.unpublish-button": {
"message": "Despublicar instancia compartida"
},
"installation-settings.shared-instance.unpublish-description": {
"message": "Borra esta instancia compartida de Modrinth y deja de enviar actualizaciones a cualquiera que la tenga. Tu instancia local no se verá afectada."
},
"installation-settings.shared-instance.unpublishing-button": {
"message": "Despublicando..."
},
"installation-settings.unlink-shared-instance.modal.admonition-body": {
"message": "Esto solo afecta a tu instancia local. Tu contenido instalado se quedará en este dispositivo, y la instancia compartida, junto a todos los que la estén usando, no se verán afectados."
},
"installation-settings.unlink-shared-instance.modal.admonition-header": {
"message": "Desvinculando instancia compartida"
},
"installation-settings.unlink-shared-instance.modal.header": {
"message": "Desvincular instancia compartida"
},
"installation-settings.unpublish-shared-instance.modal.admonition-body": {
"message": "Esto borrará la instancia compartida de los servidores de Modrinth. La gente que esté usando esta instancia dejará de recibir actualizaciones, pero tu instancia local y sus contenidos se quedarán en este dispositivo."
},
"installation-settings.unpublish-shared-instance.modal.admonition-header": {
"message": "Despublicando instancia compartida"
},
"installation-settings.unpublish-shared-instance.modal.header": {
"message": "Despublicar instancia compartida"
},
"instance.action.create-shortcut": {
"message": "Crear acceso directo"
},
"instance.action.export-modpack": {
"message": "Exportar modpack"
},
"instance.action.launch-instance": {
"message": "Iniciar instancia"
},
"instance.action.more-actions": {
"message": "Más acciones"
},
"instance.action.open-folder": {
"message": "Abrir carpeta"
},
"instance.action.repair": {
"message": "Reparar"
},
"instance.action.settings": {
"message": "Ajustes de la instancia"
},
@@ -1178,6 +1484,12 @@
"instance.settings.sharing.active-invites.code": {
"message": "Link de invitación"
},
"instance.settings.sharing.active-invites.description": {
"message": "Cualquiera con alguno de estos links se puede unir mientras el link esté activo."
},
"instance.settings.sharing.active-invites.empty": {
"message": "No hay invitaciones activas."
},
"instance.settings.sharing.active-invites.expires": {
"message": "Expira"
},
@@ -1193,6 +1505,9 @@
"instance.settings.sharing.active-invites.uses": {
"message": "Usos"
},
"instance.settings.sharing.revoke-invite.admonition-body": {
"message": "El link de invitación <monospace>{code}</monospace> dejará de funcionar inmediatamente. Cualquiera que ya se haya unido mantendrá su acceso."
},
"instance.settings.sharing.revoke-invite.admonition-header": {
"message": "Esta acción no se puede deshacer"
},
@@ -1310,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Hooks de inicio del juego"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Todas estas se ejecutan en el directorio de trabajo de la instancia, con las siguientes variables:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: La ruta absoluta a la carpeta de la instancia"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: El nombre la carpeta de la instancia"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: Los Argumentos JVM dados al juego"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: La ruta absoluta a el binario de Java"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: Un alias que apunta a $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: El nombre de la instancia"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
@@ -1325,6 +1661,9 @@
"instance.settings.tabs.installation.loader-version": {
"message": "Versión de {loader}"
},
"instance.settings.tabs.installation.locked": {
"message": "Los ajustes de instalación no están disponibles mientras esta instancia esté bloqueada."
},
"instance.settings.tabs.java": {
"message": "Java y memoria"
},
@@ -1364,6 +1703,9 @@
"instance.settings.tabs.java.java-path-placeholder": {
"message": "/ruta/de/java"
},
"instance.settings.tabs.sharing": {
"message": "Compartir"
},
"instance.settings.tabs.window": {
"message": "Ventana"
},
@@ -1394,14 +1736,62 @@
"instance.settings.tabs.window.width.enter": {
"message": "Ingresa el ancho..."
},
"instance.shared-instance.error.title": {
"message": "Algo ha salido mal"
},
"instance.shared-instance.network-error.text": {
"message": "No se pudo conectar con la API de las instancias compartidas"
},
"instance.shared-instance.network-error.title": {
"message": "Error de conexión"
},
"instance.shared-instance.owner-tooltip": {
"message": "El contenido de la instancia se está compartiendo a otros usuarios."
},
"instance.shared-instance.publish-review.added-label": {
"message": "Se añadió"
},
"instance.shared-instance.publish-review.admonition-header": {
"message": "Enviar actualización a los jugadores"
},
"instance.shared-instance.publish-review.config-title-v2": {
"message": "Seleccionar archivos de configuración"
},
"instance.shared-instance.publish-review.description": {
"message": "Revisa los cambios de contenido y escoge cualquier archivo de configuración que quieras incluir en esta actualización."
},
"instance.shared-instance.publish-review.header": {
"message": "Revisar cambios"
},
"instance.shared-instance.publish-review.publish-button": {
"message": "Enviar actualización"
},
"instance.shared-instance.publish-review.removed-label": {
"message": "Se removió"
"message": "Se eliminó"
},
"instance.shared-instance.tooltip": {
"message": "El contenido de esta instancia está siendo administrado por alguien más."
},
"instance.shared-instance.unavailable.access-revoked-text": {
"message": "Tu acceso a la instancia compartida fue revocado. Esta instancia seguirá disponible, pero ya no está vinculada, y no recibirás más actualizaciones."
},
"instance.shared-instance.unavailable.deleted-text": {
"message": "La instancia principal fue borrada. Esta instancia seguirá disponible, pero ya no está vinculada, y no recibirás más actualizaciones."
},
"instance.shared-instance.unavailable.locked-text": {
"message": "Esta instancia compartida fue bloqueada por el equipo de Moderación de Contenido. Ya no recibirá más actualizaciones de la instancia principal y tampoco se puede jugar."
},
"instance.shared-instance.unavailable.locked-title": {
"message": "Instancia bloqueada"
},
"instance.shared-instance.unavailable.manager-fallback": {
"message": "el administrador de la instancia"
},
"instance.shared-instance.unavailable.text": {
"message": "Tu instancia local sigue disponible, pero ya no está vinculada y no recibirá actualizaciones."
},
"instance.shared-instance.unavailable.title": {
"message": "Instancia compartida no disponible"
},
"instance.worlds.a_minecraft_server": {
"message": "Un servidor de Minecraft"
@@ -1466,6 +1856,12 @@
"minecraft-account.sign-in": {
"message": "Inicia sesión en Minecraft"
},
"minecraft-required.description": {
"message": "Necesitas una cuenta de Microsoft con Minecraft comprado para poder iniciar el juego."
},
"minecraft-required.description-header": {
"message": "Inicia sesión en Microsoft"
},
"minecraft-required.dont-have-account": {
"message": "¿No tienes una cuenta?"
},
@@ -1479,13 +1875,28 @@
"message": "Minecraft requerido"
},
"minecraft-required.sign-in": {
"message": "Iniciar sesión con Microsoft"
"message": "Inicia sesión en Microsoft"
},
"modal.modrinth-account-required.browser-description": {
"message": "Se abrió una pestaña para que inicies sesión. Completa el proceso ahí, y luego regresa a la app."
},
"modal.modrinth-account-required.continue-in-browser-heading": {
"message": "Continúa en el navegador"
},
"modal.modrinth-account-required.create-account-button": {
"message": "Crea una cuenta"
},
"modal.modrinth-account-required.description": {
"message": "Necesitas iniciar sesión con tu cuenta Modrinth antes de que puedas usar esto."
},
"modal.modrinth-account-required.header": {
"message": "Cuenta requerida"
},
"modal.modrinth-account-required.open-browser-again-button": {
"message": "Abrir el navegador otra vez"
},
"modal.modrinth-account-required.sign-in-button": {
"message": "Iniciar sesión en Modrinth"
"message": "Inicia sesión en Modrinth"
},
"modal.modrinth-account-required.sign-in-heading": {
"message": "Iniciar sesión con una cuenta de Modrinth"
@@ -1496,6 +1907,9 @@
"modal.modrinth-account-required.support-prompt": {
"message": "¿Tienes problemas con al iniciar sesión? <support>Consigue ayuda</support>"
},
"modal.modrinth-account-required.waiting-for-browser": {
"message": "Esperando la confirmación del navegador..."
},
"search.filter.locked.instance": {
"message": "Proporcionado por la instancia"
},
@@ -32,6 +32,15 @@
"app.action-bar.install.summary.bad-modpack-file": {
"message": "No se pudo leer el modpack"
},
"app.action-bar.install.summary.canceled": {
"message": "Cancelado"
},
"app.action-bar.install.summary.content-download-failed": {
"message": "No se pudieron descargar los archivos"
},
"app.action-bar.install.summary.corrupt-download": {
"message": "El archivo descargado está corrupto"
},
"app.action-bar.install.summary.could-not-save-files": {
"message": "No se pudieron guardar los archivos"
},
@@ -41,6 +50,12 @@
"app.action-bar.install.summary.instance-not-found": {
"message": "La instancia no se pudo encontrar"
},
"app.action-bar.install.summary.invalid-file-path": {
"message": "La ruta de archivo no es válida"
},
"app.action-bar.install.summary.invalid-modpack": {
"message": "Los datos del modpack no son válidos"
},
"app.action-bar.install.unknown-instance": {
"message": "Instancia desconocida"
},
+126 -3
View File
@@ -512,6 +512,9 @@
"app.instance.share.sign-in.button": {
"message": "Se connecter"
},
"app.instance.share.unable-to-connect.description": {
"message": "Le service d'instance partagées n'est pas disponible pour le moment, veuillez réessayer plus tard"
},
"app.instance.share.unable-to-connect.heading": {
"message": "Impossible de se connecter"
},
@@ -587,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Aucun serveur ou monde ajouté"
},
"app.instance.worlds.refreshing": {
"message": "Actualisation..."
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Supprimer le serveur"
},
@@ -833,6 +839,9 @@
"app.settings.app-version": {
"message": "Modrinth App {version}"
},
"app.settings.default-instance-options.environment-variables.description": {
"message": "Variables d'environnement définies lors du lancement d'une instance."
},
"app.settings.default-instance-options.environment-variables.placeholder": {
"message": "Entrer les variables d'environnement..."
},
@@ -854,6 +863,9 @@
"app.settings.default-instance-options.height.title": {
"message": "Hauteur"
},
"app.settings.default-instance-options.java-arguments.description": {
"message": "Arguments transmis à Java lors du lancement d'une instance."
},
"app.settings.default-instance-options.java-arguments.placeholder": {
"message": "Entrer les arguments Java..."
},
@@ -869,6 +881,21 @@
"app.settings.default-instance-options.post-exit-hook.description": {
"message": "S'exécute après que le jeu se ferme."
},
"app.settings.default-instance-options.post-exit-hook.placeholder": {
"message": "Entrez la commande à exécuter après la fermeture..."
},
"app.settings.default-instance-options.post-exit-hook.title": {
"message": "Hook de post-fermeture"
},
"app.settings.default-instance-options.pre-launch-hook.description": {
"message": "S'exécute avant que l'instance démarre."
},
"app.settings.default-instance-options.pre-launch-hook.placeholder": {
"message": "Entrez la commande de pré-lancement..."
},
"app.settings.default-instance-options.pre-launch-hook.title": {
"message": "Hook de pré-lancement"
},
"app.settings.default-instance-options.width.description": {
"message": "La largeur de la fenêtre de jeu lors du lancement."
},
@@ -878,6 +905,15 @@
"app.settings.default-instance-options.width.title": {
"message": "Largeur"
},
"app.settings.default-instance-options.wrapper-hook.description": {
"message": "Commande utilisée pour encapsuler le processus de lancement de Minecraft."
},
"app.settings.default-instance-options.wrapper-hook.placeholder": {
"message": "Entrez la commande wrapper..."
},
"app.settings.default-instance-options.wrapper-hook.title": {
"message": "Wrapper hook"
},
"app.settings.developer-mode-button.label": {
"message": "Basculer le mode développeur"
},
@@ -896,27 +932,72 @@
"app.settings.privacy.ads-consent.intro": {
"message": "Les publicités rendent Modrinth possible et financent les rémunérations des créateurs. Nos partenaires peuvent stocker des cookies ou y accéder au sein de l'application afin de personnaliser les publicités et de mesurer les performances. Vous pouvez refuser ou gérer vos préférences ci-dessous."
},
"app.settings.privacy.discord-rich-presence.description": {
"message": "Affiche Modrinth App en tant que votre activité actuelle sur Discord. Cela n'affecte pas Rich Presence ajouté à l'instance par les mods. Nécessite un redémarrage de l'application."
},
"app.settings.privacy.discord-rich-presence.title": {
"message": "Discord Rich Presence"
},
"app.settings.privacy.telemetry.description": {
"message": "Modrinth collecte des données d'analyse et d'utilisation anonymes afin d'améliorer l'expérience utilisateur et de personnaliser votre expérience. En désactivant cette option, vous choisissez de ne plus participer et vos données ne seront plus collectées."
},
"app.settings.privacy.telemetry.title": {
"message": "Télémétrie"
},
"app.settings.resource-management.always-show-copy-details.description": {
"message": "Afficher laction Copier les détails lorsquune installation est en attente ou en cours. Elle est toujours disponible pour les installations échouées ou interrompues."
},
"app.settings.resource-management.always-show-copy-details.title": {
"message": "Toujours afficher les détails de copie"
},
"app.settings.resource-management.app-cache.confirm.description": {
"message": "L'application peut se charger plus lentement jusqu'à ce que le cache soit reconstruit."
},
"app.settings.resource-management.app-cache.confirm.title": {
"message": "Purger le cache de l'application ?"
},
"app.settings.resource-management.app-cache.description": {
"message": "Nettoyer des données cachées et les télécharger de nouveau depuis Modrinth. L'application peut charger plus lentement jusqu'à ce que le cache soit de nouveau reconstitué."
},
"app.settings.resource-management.app-cache.purge": {
"message": "Purger le cache"
},
"app.settings.resource-management.app-cache.title": {
"message": "Cache de l'application"
},
"app.settings.resource-management.app-database-backups.description": {
"message": "Les sauvegardes de vos données de l'application les plus importantes sont enregistrées ici au cas où vous avez besoin de les récupérer plus tard."
},
"app.settings.resource-management.app-database-backups.open-folder": {
"message": "Ouvrir le dossier de sauvegardes"
},
"app.settings.resource-management.app-database-backups.title": {
"message": "Sauvegardes de la base de données de lapplication"
},
"app.settings.resource-management.app-directory.browse": {
"message": "Parcourir pour le répertoire de l'application"
},
"app.settings.resource-management.app-directory.description": {
"message": "Où Modrinth App stocke les instances et les autres fichiers. Changer prend effet après avoir redémarré l'application."
},
"app.settings.resource-management.app-directory.select": {
"message": "Sélectionner un nouveau répertoire pour l'application"
},
"app.settings.resource-management.app-directory.title": {
"message": "Répertoire de l'application"
},
"app.settings.resource-management.maximum-concurrent-downloads.description": {
"message": "Nombre de fichiers que l'application peut télécharger simultanément. Baissez ceci si les téléchargements sont peu fiables sur votre connexion. Nécessite un redémarrage de l'application."
},
"app.settings.resource-management.maximum-concurrent-downloads.title": {
"message": "Nombre maximal de téléchargements simultanés"
},
"app.settings.resource-management.maximum-concurrent-writes.description": {
"message": "Nombre de fichiers que l'application peut écrire sur le disque simultanément. Baissez ceci si vous rencontrez fréquemment des erreurs d'écriture / lecture. Nécessite un redémarrage de l'application."
},
"app.settings.resource-management.maximum-concurrent-writes.title": {
"message": "Nombre maximal d'écritures simultanées"
},
"app.settings.sidebar.label.instances": {
"message": "Instance"
},
@@ -1103,11 +1184,20 @@
"app.skins.toggle-ears-features-on": {
"message": "Activer"
},
"app.survey.body": {
"message": "Cela vous dérangerait-il de répondre à quelques questions sur votre expérience avec l'application Modrinth ?"
},
"app.survey.footer": {
"message": "Ces retours seront transmis directement à l'équipe de Modrinth et aideront à orienter les futures mises à jour !"
},
"app.survey.no-thanks": {
"message": "Non merci"
},
"app.survey.take-survey": {
"message": "Participer au sondage"
"message": "Répondre au sondage"
},
"app.survey.title": {
"message": "Salut, utilisateur de Modrinth !"
},
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} a finie d'être téléchargée. Rechargez pour mettre à jour maintenant, ou automatiquement quand vous fermez Modrinth App."
@@ -1139,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "La version {version} a été téléchargée avec succès !"
},
"app.user.project.install-to-instance": {
"message": "Installer dans l'instance"
},
"app.world.server-modal.placeholder-address": {
"message": "exemple.modrinth.gg"
},
@@ -1388,6 +1481,9 @@
"instance.settings.sharing.active-invites.code": {
"message": "Lien d'invitation"
},
"instance.settings.sharing.active-invites.description": {
"message": "N'importe qui avec l'un de ces liens d'invitation peut rejoindre pendant qu'il reste actif."
},
"instance.settings.sharing.active-invites.empty": {
"message": "Il n'y a aucune invitation active."
},
@@ -1406,6 +1502,9 @@
"instance.settings.sharing.active-invites.uses": {
"message": "Utilisations"
},
"instance.settings.sharing.revoke-invite.admonition-body": {
"message": "Le lien d'invitation <monospace>{code}</monospace> cessera de fonctionner immédiatement. Les personnes qui ont déjà rejoint conserveront leur accès."
},
"instance.settings.sharing.revoke-invite.admonition-header": {
"message": "Cette action ne peut pas être annulée"
},
@@ -1518,11 +1617,32 @@
"message": "Exécuté avant lancement."
},
"instance.settings.tabs.hooks.pre-launch.enter": {
"message": "Entrer commande de pré-lancement..."
"message": "Entrez la commande de pré-lancement..."
},
"instance.settings.tabs.hooks.title": {
"message": "Crochets de lancement"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Les hooks sexécutent dans le répertoire de travail de linstance et disposent des variables suivantes :"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: Chemin absolu vers le dossier de l'instance"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: Le nom du dossier de l'instance"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: Les arguments java fournis au jeu"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: Le chemin absolu vers le binaire Java"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: Un alis de $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: Le nom de l'instance"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
@@ -1566,7 +1686,7 @@
"message": "Variables d'environnement"
},
"instance.settings.tabs.java.hooks": {
"message": "Crochets"
"message": "Hooks"
},
"instance.settings.tabs.java.java-arguments": {
"message": "Arguments Java"
@@ -1616,6 +1736,9 @@
"instance.shared-instance.error.title": {
"message": "Quelque chose s'est mal passé"
},
"instance.shared-instance.network-error.text": {
"message": "Impossible de se connecter à l'API des instances partagées"
},
"instance.shared-instance.network-error.title": {
"message": "Erreur de réseau"
},
+134 -125
View File
@@ -9,7 +9,7 @@
"message": "Letöltések"
},
"app.action-bar.hide-more-running-instances": {
"message": "További futó játékprofilok elrejtése"
"message": "További futó példányok elrejtése"
},
"app.action-bar.install.copied-details": {
"message": "Másolva"
@@ -21,7 +21,7 @@
"message": "Elutasítás"
},
"app.action-bar.install.open-instance": {
"message": "Játékprofil megnyitása"
"message": "Példány megnyitása"
},
"app.action-bar.install.retry": {
"message": "Újra"
@@ -51,7 +51,7 @@
"message": "A letöltés nem fejeződött be"
},
"app.action-bar.install.summary.instance-not-found": {
"message": "A játékprofil nem található"
"message": "A példány nem található"
},
"app.action-bar.install.summary.invalid-file-path": {
"message": "A fájl elérési útja érvénytelen"
@@ -87,7 +87,7 @@
"message": "Valami félrement"
},
"app.action-bar.install.unknown-instance": {
"message": "Ismeretlen játékprofil"
"message": "Ismeretlen példány"
},
"app.action-bar.install.updating-shared-content": {
"message": "Megosztott tartalom frissítése"
@@ -96,25 +96,25 @@
"message": "Telepítések"
},
"app.action-bar.make-primary-instance": {
"message": "Beállítás elsődleges játékprofilként"
"message": "Beállítás elsődleges példányként"
},
"app.action-bar.no-instances-running": {
"message": "Nincsenek futó játékprofilok"
"message": "Nincsenek futó példányok"
},
"app.action-bar.offline": {
"message": "Offline"
},
"app.action-bar.primary-instance": {
"message": "Elsődleges játékprofil"
"message": "Elsődleges példány"
},
"app.action-bar.reload-to-update": {
"message": "Frissítés elérhető"
"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": "Játékprofil leállítása"
"message": "Példány leállítása"
},
"app.action-bar.update": {
"message": "Frissítés"
@@ -123,7 +123,7 @@
"message": "Aktív letöltések megtekintése"
},
"app.action-bar.view-instance": {
"message": "Játékprofil megtekintése"
"message": "Példányok megtekintése"
},
"app.action-bar.view-logs": {
"message": "Naplók megtekintése"
@@ -192,7 +192,7 @@
"message": "Rendszerablakkeret"
},
"app.appearance-settings.show-play-time.description": {
"message": "Megjeleníti, hogy mennyi időt töltöttél egy játékprofilban."
"message": "Megjeleníti, hogy mennyi időt töltöttél egy példányban."
},
"app.appearance-settings.show-play-time.title": {
"message": "Játékidő megjelenítése"
@@ -231,13 +231,13 @@
"message": "Indítás és navigáció"
},
"app.browse.add-servers-to-instance": {
"message": "Szerver hozzáadása a játékprofilhoz"
"message": "Szerver hozzáadása az példányhoz"
},
"app.browse.add-to-an-instance": {
"message": "Hozzáadás egy játékprofilhoz"
"message": "Hozzáadás egy példányhoz"
},
"app.browse.add-to-instance": {
"message": "Hozzáadás a játékprofilhoz"
"message": "Hozzáadás a példányhoz"
},
"app.browse.add-to-instance-name": {
"message": "Hozzáadás ehhez: {instanceName}"
@@ -249,7 +249,7 @@
"message": "Már hozzá van adva"
},
"app.browse.back-to-instance": {
"message": "Vissza a játékprofilhoz"
"message": "Vissza a példányhoz"
},
"app.browse.discover-project-type": {
"message": "{projectType} keresése"
@@ -267,7 +267,7 @@
"message": "Modcsomagok"
},
"app.browse.server-instance-content-warning": {
"message": "A tartalom hozzáadása ronthatja a kompatibilitást a szerverhez való csatlakozáskor. A hozzáadott tartalom elveszik, ha frissíted a szerverprofil tartalmát."
"message": "Tartalom hozzáadása kompatibilitási problémákat okozhat a szerverhez való csatlakozáskor. A hozzáadott tartalmak a szerverpéldány tartalmának frissítésekor elvesznek, ezért ezt tartsd szem előtt."
},
"app.browse.server.installing": {
"message": "Telepítés"
@@ -300,16 +300,16 @@
"message": "A tartalom letöltése..."
},
"app.install.phase.downloading_minecraft": {
"message": "A Minecraft letöltése..."
"message": "Minecraft letöltése"
},
"app.install.phase.downloading_pack_file": {
"message": "A csomagfájl letöltése..."
"message": "Csomagfájlok letöltése"
},
"app.install.phase.extracting_overrides": {
"message": "Felülírások kibontása..."
},
"app.install.phase.finalizing": {
"message": "Befejezés..."
"message": "Befejezés"
},
"app.install.phase.preparing_instance": {
"message": "Telepítésre vár..."
@@ -330,7 +330,7 @@
"message": "A Java {version} előkészítése..."
},
"app.install.phase.preparing_java.validating": {
"message": "A Java {version} ellenőrzése..."
"message": "A Java {version} ellenőrzése"
},
"app.install.phase.reading_pack_manifest": {
"message": "A modcsomag leírófájljának beolvasása..."
@@ -345,7 +345,7 @@
"message": "A tartalom meghatározása..."
},
"app.install.phase.rolling_back": {
"message": "Visszavonás..."
"message": "Visszavonás"
},
"app.install.phase.running_loader_processors": {
"message": "Betöltőfolyamatok futtatása..."
@@ -354,7 +354,7 @@
"message": "Hozzáadva"
},
"app.instance.admonitions.shared-instance.changes-body": {
"message": "A te gépeden lévő játékprofil újabb, mint azoké a felhasználóké, akikkel megosztottad."
"message": "A te gépeden lévő példány újabb, mint azoké a felhasználóké, akikkel megosztottad."
},
"app.instance.admonitions.shared-instance.changes-header": {
"message": "A változtatásaid még nem lettek megosztva"
@@ -372,7 +372,7 @@
"message": "Frissítés közzététele"
},
"app.instance.admonitions.shared-instance.review-description": {
"message": "A játékprofilt használó összes résztvevővel megosztásra kerülő tartalomváltoztatások áttekintése."
"message": "A példányt használó összes résztvevővel megosztásra kerülő tartalomváltoztatások áttekintése."
},
"app.instance.admonitions.shared-instance.review-header": {
"message": "Változtatások áttekintése"
@@ -384,25 +384,25 @@
"message": "Áttekintés..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Egy frssítés szükséges a(z) {name} játékprofilhoz. Kérjük, frissítsd a játékot a legújabb verzióra az indításhoz."
"message": "Egy frssítés szükséges a(z) {name} példányhoz. Kérjük, frissítsd a játékot a legújabb verzióra az indításhoz."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Frissítés érhető el"
},
"app.instance.confirm-delete.admonition-body": {
"message": "A játékprofilhoz tartozó összes adat véglegesen törlődik, beleértve a világokat, a beállításokat és az összes telepített tartalmat."
"message": "A példányodhoz tartozó összes adat véglegesen törlődik, beleértve a világjaidat, a beállításaidat és az összes telepített tartalmat."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Ezt a műveletet nem lehet visszavonni"
},
"app.instance.confirm-delete.delete-button": {
"message": "Játékprofil törlése"
"message": "Példány törlése"
},
"app.instance.confirm-delete.header": {
"message": "Játékprofil törlése"
"message": "Példány törlése"
},
"app.instance.modpack-already-installed.body": {
"message": "Ez a modcsomag már telepítve van a(z) <bold>{instanceName}</bold> játékprofilban. Biztosan duplikálni szeretnéd?"
"message": "Ez a modcsomag már telepítve van a(z) <bold>{instanceName}</bold> példányban. Biztosan meg szeretnéd kettőzni?"
},
"app.instance.modpack-already-installed.create": {
"message": "Létrehozás"
@@ -411,7 +411,7 @@
"message": "A modcsomag már telepítve van"
},
"app.instance.modpack-already-installed.instance": {
"message": "Játékprofil"
"message": "Példány"
},
"app.instance.mods.bulk-update.downloading-projects": {
"message": "{current, number}/{total, number} projekt letöltése..."
@@ -426,7 +426,7 @@
"message": "projekt"
},
"app.instance.mods.locked-content": {
"message": "Zárolt játékprofilokban a tartalom nem változtatható meg."
"message": "Zárolt példányokban a tartalom nem változtatható meg."
},
"app.instance.mods.project-was-added": {
"message": "„{name}” hozzá lett adva"
@@ -444,7 +444,7 @@
"message": "Sikeresen feltöltve"
},
"app.instance.share.empty.description": {
"message": "Ezt a játékprofilt megoszthatod a barátaiddal!"
"message": "Ezt a példányt megoszthatod a barátaiddal!"
},
"app.instance.share.empty.heading": {
"message": "Nincsenek barátok meghívva"
@@ -456,7 +456,7 @@
"message": "{name} megosztása"
},
"app.instance.share.invite-modal.user-limit-reached": {
"message": "Ez a játékprofil elérte a {limit} felhasználós korlátot."
"message": "Ez a példány elérte a {limit} felhasználós korlátot."
},
"app.instance.share.locked.empty-description-prefix": {
"message": "Be kell jelentkezned mint"
@@ -483,16 +483,16 @@
"message": "Nem található a keresésnek megfelelő felhasználó"
},
"app.instance.share.remove-user-modal.effect-access": {
"message": "Többé nem fog frissítéseket kapni ehhez a megosztott játékprofilhoz"
"message": "Többé nem fog frissítéseket kapni ehhez a megosztott példányhoz"
},
"app.instance.share.remove-user-modal.effect-installed-copy": {
"message": "A már telepített játékprofilok megmaradnak az eszközén"
"message": "A már telepített példányok megmaradnak az eszközökön"
},
"app.instance.share.remove-user-modal.effect-invite-again": {
"message": "Később újra meghívhatod"
},
"app.instance.share.remove-user-modal.effect-last-user": {
"message": "Ő az utolsó felhasználó, a megosztás ezen az játékprofilon le lesz állítva"
"message": "Ő az utolsó felhasználó, a megosztás ezen az példányon le lesz állítva"
},
"app.instance.share.remove-user-modal.effects-label": {
"message": "Mi fog történni?"
@@ -507,40 +507,40 @@
"message": "{username} avatárja"
},
"app.instance.share.remove-user-modal.warning-body": {
"message": "Ha visszavonod {username} hozzáférését ehhez a megosztott játékprofilhoz, újra meg kell hívnod, mielőtt frissítéseket kaphatna."
"message": "Ha megvonod {username} hozzáférését ehhez a megosztott példányhoz, újra meg kell hívnod, mielőtt frissítéseket kaphatna."
},
"app.instance.share.sign-in.button": {
"message": "Bejelentkezés"
},
"app.instance.share.unable-to-connect.description": {
"message": "A megosztott játékprofilok szolgáltatás pillanatnyilag nem érhető el. Kérjük, próbáld újra később"
"message": "A megosztott példányok szolgáltatás pillanatnyilag nem érhető el. Kérjük, próbáld újra később"
},
"app.instance.share.unable-to-connect.heading": {
"message": "Nem sikerült csatlakozni"
},
"app.instance.share.unlink.body": {
"message": "A játékprofil megosztásához le kell választanod ezt a modcsomagot"
"message": "A példány megosztásához le kell választanod ezt a modcsomagot"
},
"app.instance.share.unlink.header": {
"message": "A megosztáshoz le kell választani"
},
"app.instance.shared-instance-already-installed.body": {
"message": "Ez a megosztott játékprofil már telepítve van <bold>{instanceName} </bold> néven. Biztosan telepíteni szeretnél egy másik másolatot?"
"message": "Ez a megosztott példány már telepítve van <bold>{instanceName} </bold> néven. Biztosan telepíteni szeretnél egy másik másolatot?"
},
"app.instance.shared-instance-already-installed.header": {
"message": "A megosztott játékprofil már telepítve van"
"message": "A megosztott példány már telepítve van"
},
"app.instance.shared-instance-already-installed.install-anyway": {
"message": "Telepítés mindenképp"
},
"app.instance.shared-instance-already-installed.instance": {
"message": "Játékprofil"
"message": "Példány"
},
"app.instance.shared-instance-wrong-account.fallback-username": {
"message": "az összekapcsolt fiók"
},
"app.instance.shared-instance-wrong-account.owner-admonition-body-v2": {
"message": ", hogy kezelhesd ezt a megosztott játékprofilt. Nem fogod tudni közzétenni a frissítéseket."
"message": ", hogy kezelhesd ezt a megosztott példányt. Nem fogod tudni közzétenni a frissítéseket."
},
"app.instance.shared-instance-wrong-account.sign-in-as-label": {
"message": "Jelentkezz be mint"
@@ -549,7 +549,7 @@
"message": "Be kell jelentkezned a Modrinthba"
},
"app.instance.shared-instance-wrong-account.user-admonition-body-v2": {
"message": ", hogy frissítéseket kapj ehhez a megosztott játékprofilhoz."
"message": ", hogy frissítéseket kapj ehhez a megosztott példányhoz."
},
"app.instance.shared-instance-wrong-account.warning-header": {
"message": "Nem a megfelelő Modrinth-fiókot használod"
@@ -567,7 +567,7 @@
"message": "Világ törlése"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Ez a világ véglegesen törölve lesz ebből a játékprofilból. Ezt NEM tudod visszavonni!"
"message": "Ez a világ véglegesen törölve lesz ebből a példányból. Ezt NEM tudod visszavonni!"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "{name} törlése"
@@ -588,7 +588,7 @@
"message": "Kezdéshez adj hozzá egy szervert, vagy böngéssz"
},
"app.instance.worlds.no-worlds-heading": {
"message": "Nincs szerver vagy világ"
"message": "Még nincs hozzáadva szerver vagy világ"
},
"app.instance.worlds.refreshing": {
"message": "Újratöltés..."
@@ -603,7 +603,7 @@
"message": "Ez a szerver el lesz távolítva a szerverlistádból és a játékbeli szerverlistából is. Később újra hozzáadhatod, ha tudod a címet."
},
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "{name} törlése"
"message": "{name} eltávolítása"
},
"app.instance.worlds.search-worlds-placeholder": {
"message": "Keresés {count} világ között..."
@@ -663,10 +663,10 @@
"message": "Felhasználó letiltása"
},
"app.modal.install-to-play.content-you-are-reporting": {
"message": "A játékprofil, amelyet bejelenteni kívánsz"
"message": "A példány, amelyet bejelenteni kívánsz"
},
"app.modal.install-to-play.delete-instance": {
"message": "Játékprofil törlése"
"message": "Példány törlése"
},
"app.modal.install-to-play.external-files-dont-install": {
"message": "Ne telepítse"
@@ -690,7 +690,7 @@
"message": "{count} mod"
},
"app.modal.install-to-play.report-description": {
"message": "Ezzel az űrlapmal jelentsd be azokat az eseteket, amelyek megsérthetik a <rules-link>Szabályzatunkat</rules-link> vagy a <terms-link>Felhasználási feltételeinket</terms-link>."
"message": "Ezt az űrlapot használva olyan példányokat jelenthetsz, amelyek megsérthetik a <rules-link>Szabályzatunkat</rules-link> vagy a <terms-link>Felhasználási feltételeinket</terms-link>."
},
"app.modal.install-to-play.report-image-invalid-type": {
"message": "A fájl nem támogatott képfájlformátumot használ"
@@ -702,7 +702,7 @@
"message": "A DMCA-bejelentésekkel és egyéb jogi igényekkel kapcsolatban lásd a <copyright-link>Szerzői jogi szabályzatunkat</copyright-link>."
},
"app.modal.install-to-play.report-reason": {
"message": "Melyik szabályt sérti meg ez az adott játékprofil?"
"message": "Melyik szabályt sérti ez az adott példány?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Nem megfelelő"
@@ -714,7 +714,7 @@
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Megosztott játékprofil jelentése"
"message": "Megosztott példány jelentése"
},
"app.modal.install-to-play.report-submitted": {
"message": "Jelentés elküldve"
@@ -723,19 +723,19 @@
"message": "Ha segítségre van szükséged, vedd fel a kapcsolatot a <support-link>támogatási csapatunkkal</support-link>. Hibajelentésekhez nyiss egy <github-link>GitHub-issue-t</github-link>."
},
"app.modal.install-to-play.reviewed-files": {
"message": "Egy fájlt csak akkor vizsgálnak meg, ha azt a Modrinth-en közzétették, függetlenül a fájlformátumtól (beleértve a .mrpack formátumot is)."
"message": "Egy fájlt csak akkor vizsgálnak át, ha azt a Modrinthen közzétették, függetlenül a fájlformátumtól (beleértve az `.mrpack` formátumot is)."
},
"app.modal.install-to-play.shared-instance": {
"message": "Megosztott játékprofil"
"message": "Megosztott példány"
},
"app.modal.install-to-play.shared-instance-content": {
"message": "A megosztott játékprofil tartalma"
"message": "Megosztott példány tartalma"
},
"app.modal.install-to-play.shared-instance-unknown-files-description": {
"message": "Ez a megosztott játékprofil olyan fájlokat tartalmaz, amelyek nincsenek közzétéve a Modrinthon. Határozottan javasoljuk, hogy kizárólag olyan forrásokból telepíts fájlokat, amelyekben megbízol."
"message": "Ez a megosztott példány olyan fájlokat tartalmaz, amelyek nincsenek közzétéve a Modrinth-on. Határozottan javasoljuk, hogy kizárólag olyan forrásokból telepíts fájlokat, amelyekben megbízol."
},
"app.modal.install-to-play.unknown-files-description": {
"message": "Ez a szerver-modpack olyan fájlokat tartalmaz, amelyek nem szerepelnek a Modrinth oldalon. Határozottan javasoljuk, hogy kizárólag olyan forrásokból telepíts fájlokat, amelyekben megbízol."
"message": "Ez a szervermodcsomag olyan fájlokat tartalmaz, amelyeket nem tettek közzé a Modrinthen. Nyomatékosan javasoljuk, hogy csak megbízható forrásból származó fájlokat telepíts."
},
"app.modal.install-to-play.unknown-files-warning": {
"message": "Figyelmeztetés ismeretlen fájlokról"
@@ -765,7 +765,7 @@
"message": "Eltávolítva"
},
"app.modal.update-to-play.shared-instance-unknown-files-description": {
"message": "Ez a megosztott játékprofil-frissítés olyan fájlokat tartalmaz, amelyek nincsenek közzétéve a Modrinthon. Határozottan javasoljuk, hogy kizárólag olyan forrásokból telepíts fájlokat, amelyekben megbízol."
"message": "Ez a megosztott példányfrissítés olyan fájlokat tartalmaz, amelyek nincsenek közzétéve a Modrinth-on. Határozottan javasoljuk, hogy kizárólag olyan forrásokból telepíts fájlokat, amelyekben megbízol."
},
"app.modal.update-to-play.update-required": {
"message": "Frissítés szükséges"
@@ -774,7 +774,7 @@
"message": "Egy frssítés szükséges a(z) {name} játékprofilhoz. Kérjük, frissítsd a játékot a legújabb verzióra az indításhoz."
},
"app.nav.create-new-instance": {
"message": "Új játékprofil létrehozása"
"message": "Új példány létrehozása"
},
"app.nav.home": {
"message": "Kezdőlap"
@@ -813,7 +813,7 @@
"message": "Vissza a böngészéshez"
},
"app.project.install-context.back-to-instance": {
"message": "Vissza a játékprofilhoz"
"message": "Vissza a példányhoz"
},
"app.project.version.all-versions": {
"message": "Összes verzió"
@@ -828,7 +828,7 @@
"message": "Már telepítve van"
},
"app.quick-instance-switcher.drag-show-tooltip": {
"message": "Húzd el a legutóbbi játékprofilok megjelenítéséhez"
"message": "Húzd el a legutóbbi példányok megjelenítéséhez"
},
"app.quick-instance-switcher.drag-tooltip": {
"message": "Átméretezés húzással"
@@ -840,7 +840,7 @@
"message": "Modrinth App {version}"
},
"app.settings.default-instance-options.environment-variables.description": {
"message": "A játékprofil indításakor beállított környezeti változók."
"message": "A példány indításakor beállított környezeti változók."
},
"app.settings.default-instance-options.environment-variables.placeholder": {
"message": "Adj meg környezeti változókat..."
@@ -849,7 +849,7 @@
"message": "Környezeti változók"
},
"app.settings.default-instance-options.fullscreen.description": {
"message": "A játékprofilok elindítása teljes képernyős módban az options.txt fájl frissítésével."
"message": "A példányok elindítása teljes képernyős módban az options.txt fájl frissítésével."
},
"app.settings.default-instance-options.fullscreen.title": {
"message": "Teljes képernyő"
@@ -864,7 +864,7 @@
"message": "Magasság"
},
"app.settings.default-instance-options.java-arguments.description": {
"message": "A Javának a játékprofil indításakor átadott paraméterek."
"message": "A Javának a példány indításakor átadott paraméterek."
},
"app.settings.default-instance-options.java-arguments.placeholder": {
"message": "Java-indítási paraméterek megadása..."
@@ -873,7 +873,7 @@
"message": "Java-indítási paraméterek"
},
"app.settings.default-instance-options.memory-allocation.description": {
"message": "Az egyes játékprofilok számára rendelkezésre álló maximális memória."
"message": "Az egyes példányok számára rendelkezésre álló maximális memória."
},
"app.settings.default-instance-options.memory-allocation.title": {
"message": "Memóriakeret"
@@ -888,7 +888,7 @@
"message": "Kilépés utáni parancs"
},
"app.settings.default-instance-options.pre-launch-hook.description": {
"message": "A játékprofil indítása előtt fut le."
"message": "A példány indítása előtt fut le."
},
"app.settings.default-instance-options.pre-launch-hook.placeholder": {
"message": "Adj meg egy indítás előtti parancsot..."
@@ -933,7 +933,7 @@
"message": "A hirdetések teszik lehetővé a Modrinth működését, és biztosítják a tartalomkészítők juttatásait. Partnereink sütiket tárolhatnak vagy érhetnek el az alkalmazásban a hirdetések személyre szabása és a teljesítmény mérése céljából. Alább leiratkozhatsz erről, illetve beállíthatod a preferenciáidat."
},
"app.settings.privacy.discord-rich-presence.description": {
"message": "A Modrinth App megjelenítése aktuális tevékenységként a Discordon. Ez nem érinti a modok által a játékprofilokhoz hozzáadott Rich Presence-t. Az alkalmazás újraindítását igényli."
"message": "A Modrinth App megjelenítése aktuális tevékenységként a Discordon. Ez nem érinti a modok által a példányokhoz hozzáadott Rich Presence-t. Az alkalmazás újraindítását igényli."
},
"app.settings.privacy.discord-rich-presence.title": {
"message": "Discord Rich Presence"
@@ -978,7 +978,7 @@
"message": "Alkalmazáskönyvtár tallózása"
},
"app.settings.resource-management.app-directory.description": {
"message": "A Modrinth App ebben a mappában tárolja a játékprofilokat és az egyéb fájlokat. A módosítások az alkalmazás újraindítása után lépnek érvénybe."
"message": "A Modrinth App ebben a mappában tárolja a példányokat és az egyéb fájlokat. A módosítások az alkalmazás újraindítása után lépnek érvénybe."
},
"app.settings.resource-management.app-directory.select": {
"message": "Új alkalmazáskönyvtár kiválasztása"
@@ -999,7 +999,7 @@
"message": "Maximális egyidejű írási műveletek"
},
"app.settings.sidebar.label.instances": {
"message": "Játékprofilok"
"message": "Példányok"
},
"app.settings.tabs.appearance": {
"message": "Megjelenés"
@@ -1041,7 +1041,7 @@
"message": "Ezzel a kiválasztott kinézet véglegesen törlődik. Ez a művelet visszafordíthatatlan."
},
"app.skins.delete-modal.title": {
"message": "Biztos, hogy le akarod törölni ezt a kinézetet?"
"message": "Biztosan törölni szeretnéd ezt a kinézetet?"
},
"app.skins.dropped-file-error.text": {
"message": "Nem sikerült beolvasni a feltöltött fájlt."
@@ -1110,7 +1110,7 @@
"message": "Előnézet"
},
"app.skins.rate-limit.text": {
"message": "Túl gyakran cseréled a kinézetedet. A Mojang szerverei ideiglenesen letiltották a további cseréket. Kérlek várj egy pillanatot, majd próbáld meg újra."
"message": "Túl gyakran cseréled a kinézetedet. A Mojang szerverei ideiglenesen letiltották a további kéréseket. Várj egy kis ideig, majd próbáld újra."
},
"app.skins.rate-limit.title": {
"message": "Lassíts!"
@@ -1200,13 +1200,13 @@
"message": "Hali, Modrinth-felhasználó!"
},
"app.update-popup.body.download-complete": {
"message": "A Modrinth App v{version} letöltése befejeződött. Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás a bezárásakor automatikusan frissül."
"message": "A Modrinth App v{version} letöltése befejeződött. Válaszd a Frissítés lehetőséget a frissítéshez, vagy az alkalmazás bezárásakor automatikusan frissül."
},
"app.update-popup.body.linux": {
"message": "A Modrinth App v{version} elérhető. Használd a csomagkezelőt a legújabb funkciók és javítások frissítéséhez!"
"message": "A Modrinth App v{version} elérhető. A legújabb funkciókért és javításokért frissíts a csomagkezelőddel!"
},
"app.update-popup.body.metered": {
"message": "A Modrinth App v{version} már elérhető! Mivel mérhető hálózaton vagy, nem töltöttük le automatikusan."
"message": "A Modrinth App v{version} már elérhető! Mivel korlátozott adatforgalmú hálózaton vagy, nem töltöttük le automatikusan."
},
"app.update-popup.changelog": {
"message": "Változásnapló"
@@ -1229,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Verzió {version} sikeresen telepítve!"
},
"app.user.project.install-to-instance": {
"message": "Telepítés a példányba"
},
"app.world.server-modal.placeholder-address": {
"message": "pelda.modrinth.gg"
},
@@ -1248,10 +1251,10 @@
"message": "{count} online"
},
"content.shared-instance.change-version-body": {
"message": "A verzió módosítása csak a helyi játékprofilt érinti. A megosztott játékprofil jövőbeli frissítései felülírhatják vagy ismét megváltoztathatják azt."
"message": "A verzió módosítása csak a helyi példányt érinti. A megosztott példány jövőbeli frissítései felülírhatják vagy ismét megváltoztathatják azt."
},
"content.shared-instance.delete-bulk-body": {
"message": "A kiválasztott projektek egy része a megosztott játékprofil része. Törlésük csak a helyi játékprofilt módosítja, és a megosztott játékprofil jövőbeli frissítései visszaállíthatják vagy ismét módosíthatják őket."
"message": "A kiválasztott projektek egy része a megosztott példány része. Törlésük csak a helyi példányt módosítja, és a megosztott példány jövőbeli frissítései visszaállíthatják vagy ismét módosíthatják őket."
},
"content.shared-instance.delete-button": {
"message": "Törlés mindenképp"
@@ -1260,10 +1263,10 @@
"message": "{count, number} projekt törlése mindenképp"
},
"content.shared-instance.delete-single-body": {
"message": "A törlése csak a helyi játékprofilt módosítja. A megosztott játékprofil jövőbeli frissítései visszaállíthatják vagy ismét módosíthatják."
"message": "A törlése csak a helyi példányt módosítja. A megosztott példány jövőbeli frissítései visszaállíthatják vagy ismét módosíthatják."
},
"content.shared-instance.disable-bulk-body": {
"message": "A kiválasztott projektek egy része a megosztott játékprofil része. Letiltásuk csak a helyi játékprofilt módosítja, és a megosztott játékprofil jövőbeli frissítései újra engedélyezhetik, visszaállíthatják vagy ismét módosíthatják őket."
"message": "A kiválasztott projektek egy része a megosztott példány része. Letiltásuk csak a helyi példányt módosítja, és a megosztott példány jövőbeli frissítései újra engedélyezhetik, visszaállíthatják vagy ismét módosíthatják őket."
},
"content.shared-instance.disable-button": {
"message": "Letíltás mindenképp"
@@ -1272,13 +1275,13 @@
"message": "{count, number} projekt letíltása mindenképp"
},
"content.shared-instance.disable-single-body": {
"message": "A letiltása csak a helyi játékprofilt módosítja. A megosztott játékprofil jövőbeli frissítései újra engedélyezhetik, visszaállíthatják vagy ismét módosíthatják."
"message": "A letiltása csak a helyi példányt módosítja. A megosztott példány jövőbeli frissítései újra engedélyezhetik, visszaállíthatják vagy ismét módosíthatják."
},
"content.shared-instance.unlink-body": {
"message": "A leválasztás csak a helyi játékprofilt érinti. A megosztott játékprofil jövőbeli frissítései felülírhatják vagy ismét megváltoztathatják azt."
"message": "A leválasztás csak a helyi példányt érinti. A megosztott példány jövőbeli frissítései felülírhatják vagy ismét megváltoztathatják azt."
},
"content.shared-instance.warning-header": {
"message": "Ez egy megosztott játékprofil része"
"message": "Ez egy megosztott példány része"
},
"friends.action.add-friend": {
"message": "Barát hozzáadása"
@@ -1344,46 +1347,46 @@
"message": "<link>Lépj be Modrinth fiókodba</link>, hogy felvehess barátokat és lásd mivel játszanak!"
},
"installation-settings.shared-instance.linked-title": {
"message": "Összekapcsolt megosztott játékprofil"
"message": "Összekapcsolt megosztott példány"
},
"installation-settings.shared-instance.title": {
"message": "Játékprofil megosztásának visszavonása"
"message": "Példány megosztásának visszavonása"
},
"installation-settings.shared-instance.unlink-button": {
"message": "Megosztott játékprofil leválasztása"
"message": "Megosztott példány leválasztása"
},
"installation-settings.shared-instance.unlink-description": {
"message": "Leválasztja ezt a helyi játékprofilt a jövőbeli megosztott frissítésekről."
"message": "Leválasztja ezt a helyi példányt a jövőbeli megosztott frissítésekről."
},
"installation-settings.shared-instance.unlinking-button": {
"message": "Leválasztás..."
},
"installation-settings.shared-instance.unpublish-button": {
"message": "Megosztott játékprofil megosztásának visszavonása"
"message": "Megosztott példány megosztásának visszavonása"
},
"installation-settings.shared-instance.unpublish-description": {
"message": "Eltávolítja ezt a megosztott játékprofilt a Modrinthból, és nem küld többé frissítéseket azoknak, akik használják. A helyi játékprofilodat ez nem érinti."
"message": "Eltávolítja ezt a megosztott példányt a Modrinthból, és nem küld többé frissítéseket azoknak, akik használják. A helyi példányodat ez nem érinti."
},
"installation-settings.shared-instance.unpublishing-button": {
"message": "Közzététel visszavonása..."
},
"installation-settings.unlink-shared-instance.modal.admonition-body": {
"message": "Ez kizárólag a helyi játékprofilodra vonatkozik. A telepített tartalmaid ezen az eszközön maradnak, és ez nem érinti a megosztott játékprofilt, illetve az azt használó többi felhasználót."
"message": "Ez kizárólag a helyi példányodra vonatkozik. A telepített tartalmaid ezen az eszközön maradnak, és ez nem érinti a megosztott példányt, illetve az azt használó többi felhasználót."
},
"installation-settings.unlink-shared-instance.modal.admonition-header": {
"message": "Megosztott játékprofil leválasztása"
},
"installation-settings.unlink-shared-instance.modal.header": {
"message": "Megosztott játékprofil leválasztása"
"message": "Megosztott példány leválasztása"
},
"installation-settings.unpublish-shared-instance.modal.admonition-body": {
"message": "Ezzel a megosztott játékprofil törlődik a Modrinth szervereiről. A Modrinth Appban az ezt használók többé nem kapnak frissítéseket, de a helyi játékprofil és annak tartalma továbbra is ezen az eszközön marad."
"message": "Ezzel a megosztott példány törlődik a Modrinth szervereiről. A Modrinth Appban az ezt használók többé nem kapnak frissítéseket, de a helyipéldány és annak tartalma továbbra is ezen az eszközön marad."
},
"installation-settings.unpublish-shared-instance.modal.admonition-header": {
"message": "Visszavonod a megosztott játékprofil megosztását"
"message": "Visszavonod a megosztott példány megosztását"
},
"installation-settings.unpublish-shared-instance.modal.header": {
"message": "Megosztott játékprofil megosztásának visszavonása"
"message": "Megosztott példány megosztásának visszavonása"
},
"instance.action.create-shortcut": {
"message": "Parancsikon létrehozása"
@@ -1392,7 +1395,7 @@
"message": "Modcsomag exportálása"
},
"instance.action.launch-instance": {
"message": "Játékprofil elindítása"
"message": "Példány elindítása"
},
"instance.action.more-actions": {
"message": "További műveletek"
@@ -1404,7 +1407,7 @@
"message": "Javítás"
},
"instance.action.settings": {
"message": "Játékprofil beállításai"
"message": "Példány beállításai"
},
"instance.action.starting": {
"message": "Indítás..."
@@ -1449,16 +1452,16 @@
"message": "Világ szerkesztése"
},
"instance.files.adding-files": {
"message": "Fájlok hozzáadása... ({completed}/{total})"
"message": "Fájlok hozzáadása ({completed}/{total})"
},
"instance.files.save-as": {
"message": "Mentés másként..."
},
"instance.locked.delete-button": {
"message": "Játékprofil törlése"
"message": "Példány törlése"
},
"instance.locked.play-tooltip": {
"message": "Ez a játékprofil zárolva van"
"message": "Ez a példány zárolva van"
},
"instance.playtime.never-played": {
"message": "Még nem játszott"
@@ -1518,13 +1521,13 @@
"message": "Általános"
},
"instance.settings.tabs.general.delete": {
"message": "Játékprofil törlése"
"message": "Példány törlése"
},
"instance.settings.tabs.general.delete.button": {
"message": "Játékprofil törlése"
"message": "Példány törlése"
},
"instance.settings.tabs.general.delete.description": {
"message": "Örökké eltávolít egy játékprofilt az eszközről, beleértve a világait, beállításait és minden telepített tartalmat. Legyél óvatos, mert ha egyszer kitörölsz egy játékprofilt, azt többé nem lehet visszaállítani."
"message": "Örökké eltávolít egy példányt az eszközről, beleértve a világait, beállításait és minden telepített tartalmat. Legyél óvatos, mert ha egyszer kitörölsz egy példányt, azt többé nem lehet visszaállítani."
},
"instance.settings.tabs.general.deleting.button": {
"message": "Törlés..."
@@ -1536,10 +1539,10 @@
"message": "Telepítés közben nem lehet duplikálni."
},
"instance.settings.tabs.general.duplicate-instance": {
"message": "Játékprofil duplikálása"
"message": "Példány megkettőzése"
},
"instance.settings.tabs.general.duplicate-instance.description": {
"message": "Készít egy másolatot erről a játékprofilról, beleértve a világokat, beállításokat, modokat stb."
"message": "Készít egy másolatot erről a példányról, beleértve a világokat, beállításokat, modokat stb."
},
"instance.settings.tabs.general.edit-icon": {
"message": "Ikon szerkesztése"
@@ -1557,10 +1560,10 @@
"message": "Könyvtárgyűjtemények"
},
"instance.settings.tabs.general.library-groups.create": {
"message": "Új gyűjtemény létrehozása"
"message": "Új csoport létrehozása"
},
"instance.settings.tabs.general.library-groups.description": {
"message": "A könyvtárgyűjtemények segítenek külön kategóriákba rendszerezni a játékprofilokat."
"message": "A könyvtárgyűjtemények segítenek külön kategóriákba rendszerezni a példányokat."
},
"instance.settings.tabs.general.library-groups.enter-name": {
"message": "Add meg a gyűjtemény nevét"
@@ -1622,6 +1625,12 @@
"instance.settings.tabs.hooks.title": {
"message": "Játék indítási horgok"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: A(z) $INST_DIR álneve"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: A példány neve"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Indítóparancs"
},
@@ -1638,7 +1647,7 @@
"message": "{loader} verzió"
},
"instance.settings.tabs.installation.locked": {
"message": "A telepítési beállítások nem érhetők el, amíg ez a játékprofil zárolva van."
"message": "A telepítési beállítások nem érhetők el, amíg ez a példány zárolva van."
},
"instance.settings.tabs.java": {
"message": "Java és memória"
@@ -1677,7 +1686,7 @@
"message": "Hozzárendelt memória"
},
"instance.settings.tabs.java.java-path-placeholder": {
"message": "/eleresi/ut/a/javahoz"
"message": "/path/to/java"
},
"instance.settings.tabs.sharing": {
"message": "Megosztás"
@@ -1716,13 +1725,13 @@
"message": "Valami hiba történt"
},
"instance.shared-instance.network-error.text": {
"message": "Nem sikerült csatlakozni a megosztott játékprofilok API-jához"
"message": "Nem sikerült csatlakozni a megosztott példányok API-jához"
},
"instance.shared-instance.network-error.title": {
"message": "Hálózati hiba"
},
"instance.shared-instance.owner-tooltip": {
"message": "Ennek a játékprofilnak a tartalma meg van osztva más felhasználókkal."
"message": "Ennek a példánynak a tartalma meg van osztva más felhasználókkal."
},
"instance.shared-instance.publish-review.added-label": {
"message": "Hozzáadva:"
@@ -1746,28 +1755,28 @@
"message": "Eltávolítva:"
},
"instance.shared-instance.tooltip": {
"message": "Ennek a játékprofilynak a tartalmát más kezeli."
"message": "Ennek a példánynak a tartalmát más kezeli."
},
"instance.shared-instance.unavailable.access-revoked-text": {
"message": "A megosztott játékprofilhoz való hozzáférésedet visszavonták. Ez a játékprofil továbbra is elérhető, de már nincs összekapcsolva, és többé nem kap frissítéseket."
"message": "A megosztott példányhoz való hozzáférésedet megvonták. Ez a példány továbbra is elérhető, de már nincs összekapcsolva, és többé nem kap frissítéseket."
},
"instance.shared-instance.unavailable.deleted-text": {
"message": "Az elsődleges játékprofilt törölték. Ez a játékprofil továbbra is elérhető, de már nincs összekapcsolva, és többé nem kap frissítéseket."
"message": "Az elsődleges példányt törölték. Ez a példány továbbra is elérhető, de már nincs összekapcsolva, és többé nem kap frissítéseket."
},
"instance.shared-instance.unavailable.locked-text": {
"message": "Ezt a megosztott játékprofilt a Tartalommoderációs csapat zárolta. Többé nem kap frissítéseket az elsődleges játékprofiltól, és nem lehet játszani vele."
"message": "Ezt a megosztott példányt a Tartalommoderációs csapat zárolta. Többé nem kap frissítéseket az elsődleges példánytól, és nem lehet játszani vele."
},
"instance.shared-instance.unavailable.locked-title": {
"message": "Játékprofil zárolva"
"message": "Példány zárolva"
},
"instance.shared-instance.unavailable.manager-fallback": {
"message": "a játékprofil-kezelő"
"message": "a példánykezelő"
},
"instance.shared-instance.unavailable.text": {
"message": "A te gépeden lévő játékprofil továbbra is elérhető, de már nincs összekapcsolva, és többé nem kap frissítéseket."
"message": "A helyi példányod továbbra is elérhető, de már nincs összekapcsolva, és többé nem kap frissítéseket."
},
"instance.shared-instance.unavailable.title": {
"message": "A megosztott játékprofil már nem elérhető"
"message": "A megosztott példány már nem elérhető"
},
"instance.worlds.a_minecraft_server": {
"message": "Egy Minecraft szerver"
@@ -1785,7 +1794,7 @@
"message": "Ne mutasd a kezdőlapon"
},
"instance.worlds.game_already_open": {
"message": "A játékprofil már el van indítva"
"message": "A példány már el van indítva"
},
"instance.worlds.hardcore": {
"message": "Hardcore mód"
@@ -1803,13 +1812,13 @@
"message": "Csak Minecraft Alpha 1.0.5+-tól tudsz egyből szerverhez csatlakozni"
},
"instance.worlds.no_singleplayer_quick_play": {
"message": "Csak Minecraft 1.20+-tól tudsz egyből egyjátékos világba belépni"
"message": "Használj 1.20+ verziót az azonnali egyjátékos módba lépéshez"
},
"instance.worlds.play_instance": {
"message": "Játékprofil indítása"
"message": "Példány indítása"
},
"instance.worlds.view_instance": {
"message": "Játékprofil megtekintése"
"message": "Példány megtekintése"
},
"instance.worlds.world_in_use": {
"message": "A világ használatban van"
@@ -1887,25 +1896,25 @@
"message": "Várakozás a böngésző megerősítésére..."
},
"search.filter.locked.instance": {
"message": "A játékprofil által van megadva"
"message": "Pédány által megadva"
},
"search.filter.locked.instance-game-version.title": {
"message": "A játékverziót az adott játékprofil biztosítja"
"message": "A játékverziót az adott példány biztosítja"
},
"search.filter.locked.instance-loader.title": {
"message": "A betöltőt az adott játékprofil biztosítja"
"message": "A betöltőt az adott példány biztosítja"
},
"search.filter.locked.instance.sync": {
"message": "Szinkronizálás a játékpéldánnyal"
"message": "Szinkronizálás a példánnyal"
},
"search.filter.locked.server": {
"message": "A szerver által van megadva"
},
"search.filter.locked.server-environment.title": {
"message": "Csak kliensoldali modok adhatók hozzá a szerverprofilhoz"
"message": "Szerverpéldányokhoz csak szerveroldali modok adhatók hozzá"
},
"search.filter.locked.server-game-version.title": {
"message": "A játékverziót az adott szerver biztosítja"
"message": "A játékverzió a szerver által van megadva"
},
"search.filter.locked.server-loader.title": {
"message": "A betöltőt az adott szerver biztosítja"
+28 -4
View File
@@ -393,7 +393,7 @@
"message": "Tutti i dati della tua istanza verranno eliminati permanentemente, inclusi i tuoi mondi, configurazioni e tutti i contenuti installati."
},
"app.instance.confirm-delete.admonition-header": {
"message": "Questa azione non può essere annullata"
"message": "Questa azione è irreversibile"
},
"app.instance.confirm-delete.delete-button": {
"message": "Elimina istanza"
@@ -564,7 +564,7 @@
"message": "Eliminare il mondo"
},
"app.instance.worlds.delete-world-modal.warning-body": {
"message": "Questo mondo verrà eliminato permanentemente dall'istanza. Questa azione non può essere annullata."
"message": "Il mondo sarà eliminato dall'istanza per sempre. Questa azione è irreversibile."
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Eliminare {name}"
@@ -1035,7 +1035,7 @@
"message": "Elimina"
},
"app.skins.delete-modal.description": {
"message": "Questa skin sarà rimossa per sempre. Quest'azione non può essere annullata."
"message": "La skin selezionata sarà eliminata per sempre. Questa azione è irreversibile."
},
"app.skins.delete-modal.title": {
"message": "Vuoi davvero eliminare questa skin?"
@@ -1226,6 +1226,9 @@
"app.update.complete-toast.title": {
"message": "La versione {version} è stata installata con successo!"
},
"app.user.project.install-to-instance": {
"message": "Installa nell'istanza"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1503,7 +1506,7 @@
"message": "L'invito <monospace>{code}</monospace> smetterà di funzionare immediatamente. Le persone già entrate manterranno l'accesso."
},
"instance.settings.sharing.revoke-invite.admonition-header": {
"message": "Questa azione non può essere annullata"
"message": "Questa azione è irreversibile"
},
"instance.settings.sharing.revoke-invite.confirm": {
"message": "Revoca invito"
@@ -1619,6 +1622,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Hook all'avvio del gioco"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Gli hook si eseguono nella cartella principale dell'istanza, con le seguenti variabili:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: Il percorso assoluto della cartella dell'istanza"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: Il nome della cartella dell'istanza"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: Gli argomenti JVM forniti al gioco"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: Il percorso assoluto dei binari di Java"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: Alias di $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: Il nome dell'istanza"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
+28 -1
View File
@@ -516,7 +516,7 @@
"message": "インスタンスの共有は現在ご利用いただけません。しばらくしてからもう一度お試しください"
},
"app.instance.share.unable-to-connect.heading": {
"message": "接続できません"
"message": "接続エラー"
},
"app.instance.share.unlink.body": {
"message": "この起動構成を共有するには、このmodpackのリンクを解除する必要があります"
@@ -590,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "サーバー、ワールドは追加されていません"
},
"app.instance.worlds.refreshing": {
"message": "更新中…"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "サーバーを削除"
},
@@ -1226,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "バージョン {version} が正常にインストールされました!"
},
"app.user.project.install-to-instance": {
"message": "インスタンスにインストール"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1619,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "ゲーム起動フック"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "フックはインスタンスのカレントディレクトリで実行され、以下の環境変数が利用可能です:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: インスタンスのフォルダへの絶対パス"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: インスタンスのフォルダ名"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: ゲームに渡されるJVM引数"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: Javaの実行ファイルへの絶対パス"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR:$INST_DIR のエイリアス"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: インスタンスの名前"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "ラッパー"
},
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Sedang memuat turun Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Sedang memuat turun kemas kini"
},
"app.action-bar.downloads": {
"message": "Muat Turun"
},
@@ -11,6 +14,9 @@
"app.action-bar.install.copied-details": {
"message": "Disalin"
},
"app.action-bar.install.copy-details": {
"message": "Salin butiran"
},
"app.action-bar.install.dismiss": {
"message": "Ketepikan"
},
@@ -590,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Noch servers noch werelden toegevoegd"
},
"app.instance.worlds.refreshing": {
"message": "Verversen..."
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Server verwijderen"
},
@@ -932,9 +935,78 @@
"app.settings.privacy.discord-rich-presence.description": {
"message": "Laat Modrinth als jouw activiteit op Discord. Dit verandert niet activiteiten toegevoegd door mods."
},
"app.settings.privacy.discord-rich-presence.title": {
"message": "Discord Rich Presence"
},
"app.settings.privacy.telemetry.description": {
"message": "Modrinth verzamelt geanonimiseerde analyse- en gebruiksgegevens om de gebruikerservaring te verbeteren en uw ervaring te personaliseren. Door deze optie uit te schakelen, meldt u zich af en worden uw gegevens niet langer verzameld."
},
"app.settings.privacy.telemetry.title": {
"message": "Telemetrie"
},
"app.settings.resource-management.always-show-copy-details.description": {
"message": "Geef de knop 'Details kopiëren' weer terwijl een installatie in de wachtrij staat of wordt uitgevoerd. Deze actie is altijd beschikbaar voor mislukte of onderbroken installaties."
},
"app.settings.resource-management.always-show-copy-details.title": {
"message": "Altijd 'Details kopiëren' laten zien"
},
"app.settings.resource-management.app-cache.confirm.description": {
"message": "De app kan wat trager laden totdat de cache opnieuw is opgebouwd."
},
"app.settings.resource-management.app-cache.confirm.title": {
"message": "De cache van de app wissen?"
},
"app.settings.resource-management.app-cache.description": {
"message": "Wis de gegevens in de cache en download ze opnieuw via Modrinth. De app kan wat trager laden totdat de cache opnieuw is opgebouwd."
},
"app.settings.resource-management.app-cache.purge": {
"message": "Cache leegmaken"
},
"app.settings.resource-management.app-cache.title": {
"message": "App-cache"
},
"app.settings.resource-management.app-database-backups.description": {
"message": "Hier worden back-ups van belangrijke app-gegevens opgeslagen voor het geval je ze later nodig hebt."
},
"app.settings.resource-management.app-database-backups.open-folder": {
"message": "Open de map met back-ups"
},
"app.settings.resource-management.app-database-backups.title": {
"message": "Back-ups van de app-database"
},
"app.settings.resource-management.app-directory.browse": {
"message": "Zoek naar een app-folder"
},
"app.settings.resource-management.app-directory.description": {
"message": "Hier slaat de Modrinth-app instanties en andere bestanden op. Wijzigingen worden pas van kracht nadat de app opnieuw is opgestart."
},
"app.settings.resource-management.app-directory.select": {
"message": "Kies een nieuwe app-folder"
},
"app.settings.resource-management.app-directory.title": {
"message": "App-folder"
},
"app.settings.resource-management.maximum-concurrent-downloads.description": {
"message": "Het aantal bestanden dat de app tegelijkertijd kan downloaden. Verlaag dit aantal als downloads via je verbinding niet betrouwbaar zijn. Hiervoor moet de app opnieuw worden opgestart."
},
"app.settings.resource-management.maximum-concurrent-downloads.title": {
"message": "Maximaal aantal gelijktijdige downloads"
},
"app.settings.resource-management.maximum-concurrent-writes.description": {
"message": "Het aantal bestanden dat de app tegelijkertijd naar de schijf kan schrijven. Verlaag deze waarde als je vaak I/O-fouten tegenkomt. Hiervoor moet de app opnieuw worden opgestart."
},
"app.settings.resource-management.maximum-concurrent-writes.title": {
"message": "Maximaal aantal gelijktijdige schrijfbewerkingen"
},
"app.settings.sidebar.label.instances": {
"message": "Instanties"
},
"app.settings.tabs.appearance": {
"message": "Uiterlijk"
},
"app.settings.tabs.behavior": {
"message": "Gedrag"
},
"app.settings.tabs.default-instance-options": {
"message": "Standaard spelopties"
},
@@ -1157,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Versie {version} is succesvol geïnstalleerd!"
},
"app.user.project.install-to-instance": {
"message": "Op instantie installeren"
},
"app.world.server-modal.placeholder-address": {
"message": "voorbeeld.modrinth.gg"
},
@@ -1427,6 +1502,21 @@
"instance.settings.sharing.active-invites.title": {
"message": "Actieve uitnodigingen"
},
"instance.settings.sharing.active-invites.uses": {
"message": "Keren gebruikt"
},
"instance.settings.sharing.revoke-invite.admonition-body": {
"message": "De uitnodigingslink <monospace>{code}</monospace> zal gelijk niet meer werken. Mensen die zich al hebben aangemeld, behouden hun toegang."
},
"instance.settings.sharing.revoke-invite.admonition-header": {
"message": "Deze actie kan niet ongedaan worden gemaakt"
},
"instance.settings.sharing.revoke-invite.confirm": {
"message": "Uitnodiging intrekken"
},
"instance.settings.sharing.revoke-invite.header": {
"message": "Uitnodiging intrekken"
},
"instance.settings.tabs.general": {
"message": "Algemeen"
},
@@ -1535,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Game opstart haakjes"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Hooks worden uitgevoerd in de werkfolder van de instantie, met de volgende variabelen:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: Het absolute pad naar de map van de instantie"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: De naam van de map van de instantie"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: De JVM-argumenten die aan het spel worden doorgegeven"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: Het absolute pad naar het Java-uitvoeringsbestand"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: Een alias voor $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: De naam van de instantie"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
@@ -1628,6 +1739,12 @@
"instance.shared-instance.error.title": {
"message": "Er is iets misgegaan"
},
"instance.shared-instance.network-error.text": {
"message": "Kan geen verbinding maken met de API voor gedeelde instanties"
},
"instance.shared-instance.network-error.title": {
"message": "Netwerkfout"
},
"instance.shared-instance.owner-tooltip": {
"message": "De inhoud van deze instantie wordt gedeeld met andere gebruikers."
},
@@ -1816,5 +1933,11 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader is gegeven door de server"
},
"settings.sidebar.label.account": {
"message": "Account"
},
"settings.sidebar.label.display": {
"message": "Weergave"
}
}
+46 -1
View File
@@ -461,9 +461,18 @@
"app.instance.share.locked.wrong-account-heading": {
"message": "Złe konto"
},
"app.instance.share.members.empty": {
"message": "Żaden użytkownik jeszcze nie dołączył"
},
"app.instance.share.members.no-filter-results": {
"message": "Nie znaleziono użytkowników pasujących do tych filtrów."
},
"app.instance.share.remove-user-modal.effect-access": {
"message": "Nie będzie w stanie otrzymywać aktualizacji dla tej udostępnionej instancji"
},
"app.instance.share.remove-user-modal.effect-last-user": {
"message": "To jest ostatni użytkownik, udostępnianie zostanie wyłączone dla tej instancji"
},
"app.instance.share.remove-user-modal.effects-label": {
"message": "Co się stanie?"
},
@@ -545,6 +554,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Nie dodano żadnych światów ani serwerów"
},
"app.instance.worlds.refreshing": {
"message": "Odświeżanie..."
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Usuń serwer"
},
@@ -602,6 +614,9 @@
"app.java-selector.path.placeholder": {
"message": "/path/to/java"
},
"app.java-selector.test-installation": {
"message": "Testuj instalację Java"
},
"app.modal.install-to-play.additional-context": {
"message": "Dodatkowy kontekst"
},
@@ -632,6 +647,9 @@
"app.modal.install-to-play.invite-warning": {
"message": "To zaproszenie zostało utworzone przez innego użytkownika Modrinth, nie przez firmę Modrinth. Tylko akceptuj zaproszenia od osób, którym ufasz."
},
"app.modal.install-to-play.invite-warning-with-creator": {
"message": "To zaproszenie zostało utworzone przez <creator>{username}</creator>, nie przez firmę Modrinth. Akceptuj zaproszenia tylko od osób, którym ufasz."
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} few {# mody} other {# modów}}"
},
@@ -677,6 +695,9 @@
"app.modal.install-to-play.shared-instance-content": {
"message": "Treści w przesłanej ci instalacji"
},
"app.modal.install-to-play.shared-instance-unknown-files-description": {
"message": "Ta udostępniona instancja zawiera pliki, które nie zostały opublikowane na Modrinth. Zalecamy instalowanie plików tylko ze źródeł, którym ufasz."
},
"app.modal.install-to-play.unknown-files-description": {
"message": "Ta serwerowa paczka modów zawiera pliki, które nie zostały opublikowane na Modrinth. Zalecamy instalowanie plików tylko ze źródeł, którym ufasz."
},
@@ -705,7 +726,7 @@
"message": "Usunięto"
},
"app.modal.update-to-play.shared-instance-unknown-files-description": {
"message": "Ta aktualizacja przesłanej instancji zawiera pliki, które nie zostały opublikowane na Modrinth. Zalecamy instalowanie tych plików z źródła, któremu ufasz."
"message": "Ta aktualizacja udostępnionej instancji zawiera pliki, które nie zostały opublikowane na Modrinth. Zalecamy instalowanie plików tylko ze źródeł, którym ufasz."
},
"app.modal.update-to-play.update-required": {
"message": "Wymagana jest aktualizacja"
@@ -863,6 +884,9 @@
"app.settings.tabs.appearance": {
"message": "Wygląd"
},
"app.settings.tabs.behavior": {
"message": "Zachowanie"
},
"app.settings.tabs.default-instance-options": {
"message": "Domyślne opcje gry"
},
@@ -1079,6 +1103,9 @@
"app.update.complete-toast.title": {
"message": "Wersja {version} została pomyślnie zainstalowana!"
},
"app.user.project.install-to-instance": {
"message": "Zainstaluj do instancji"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1178,6 +1205,9 @@
"installation-settings.shared-instance.linked-title": {
"message": "Powiązana udostępniona instancja"
},
"installation-settings.shared-instance.title": {
"message": "Wycofaj instancję"
},
"installation-settings.shared-instance.unlink-button": {
"message": "Odłącz udostępnioną instancję"
},
@@ -1187,9 +1217,21 @@
"installation-settings.shared-instance.unlinking-button": {
"message": "Odłączanie..."
},
"installation-settings.shared-instance.unpublish-button": {
"message": "Wycofaj udostępnioną instancję"
},
"installation-settings.shared-instance.unpublish-description": {
"message": "Usuń tę udostępnioną instancję z Modrinth i przestań wysyłać aktualizacje do użytkowników, którym jest udostępniona. Nie wpłynie to na twoją lokalną instancję."
},
"installation-settings.unpublish-shared-instance.modal.admonition-body": {
"message": "Spowoduje to usunięcie przesyłanej instalacji z serwerów Modrith. Użytkownicy korzystający z niej na Modrinth App przestaną otrzymywać aktualizacje, ale twoja lokalna instalacja zostanie na tym urządzeniu."
},
"installation-settings.unpublish-shared-instance.modal.admonition-header": {
"message": "Wycofywanie udostępnionej instancji"
},
"installation-settings.unpublish-shared-instance.modal.header": {
"message": "Wycofaj udostępnioną instancję"
},
"instance.action.create-shortcut": {
"message": "Utwórz skrót"
},
@@ -1259,6 +1301,9 @@
"instance.files.save-as": {
"message": "Zapisz jako..."
},
"instance.locked.delete-button": {
"message": "Usuń instancję"
},
"instance.playtime.never-played": {
"message": "Nigdy nie grano"
},
+26 -2
View File
@@ -36,7 +36,7 @@
"message": "Cancelado"
},
"app.action-bar.install.summary.cleanup-incomplete": {
"message": "A limpeza não foi finalizada"
"message": "A limpeza não foi concluída"
},
"app.action-bar.install.summary.content-download-failed": {
"message": "Não foi possível baixar os arquivos"
@@ -51,7 +51,7 @@
"message": "O download não foi concluído"
},
"app.action-bar.install.summary.instance-not-found": {
"message": "Instância não encontrada"
"message": "A instância não foi encontrada"
},
"app.action-bar.install.summary.invalid-file-path": {
"message": "O caminho do arquivo é inválido"
@@ -1229,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Versão {version} instalada!"
},
"app.user.project.install-to-instance": {
"message": "Instalar para instância"
},
"app.world.server-modal.placeholder-address": {
"message": "exemplo.modrinth.gg"
},
@@ -1622,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Ações de inicialização do jogo"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Hooks são executados no diretório de trabalho da instância, com as seguintes variáveis:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: O caminho absoluto para a pasta da instância"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: O nome da pasta da instância"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: Os argumentos JVM fornecidos ao jogo"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: O caminho absoluto ao binário Java"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: um apelido de $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: O nome da instância"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Comando auxiliar"
},
@@ -1,10 +1,130 @@
{
"app.action-bar.downloading-java": {
"message": "Se descarcă Java {version}"
},
"app.action-bar.downloading-update": {
"message": "Se descarcă actualizarea"
},
"app.action-bar.downloads": {
"message": "Descărcări"
},
"app.action-bar.install.copied-details": {
"message": "Copiat"
},
"app.action-bar.install.copy-details": {
"message": "Copiază detaliile"
},
"app.action-bar.install.dismiss": {
"message": "Ignoră"
},
"app.action-bar.install.open-instance": {
"message": "Instanța nouă"
},
"app.action-bar.install.retry": {
"message": "Reîncearcă"
},
"app.action-bar.install.summary.canceled": {
"message": "Anulat"
},
"app.action-bar.install.summary.could-not-save-files": {
"message": "Nu s-au putut salva fișierele"
},
"app.action-bar.offline": {
"message": "Offline"
},
"app.action-bar.update": {
"message": "Actualizează"
},
"app.action-bar.view-active-downloads": {
"message": "Vezi descărcările active"
},
"app.action-bar.view-instance": {
"message": "Vezi instanțele"
},
"app.action-bar.view-logs": {
"message": "Vezi jurnalele"
},
"app.ads-consent.accept": {
"message": "Acceptă tot"
},
"app.ads-consent.reject": {
"message": "Refuză tot"
},
"app.auth-servers.unreachable.body": {
"message": "Serverele de autentificare Minecraft pot fi indisponibile în acest moment. Verificați conexiunea la internet și încercați din nou mai târziu."
},
"app.auth-servers.unreachable.header": {
"message": "Nu se pot accesa serverele de autentificare"
},
"app.browse.added": {
"message": "Adăugat"
},
"app.browse.already-added": {
"message": "Deja adăugat"
},
"app.browse.back-to-instance": {
"message": "Înapoi la instanță"
},
"app.browse.discover-project-type": {
"message": "Descoperă {projectType}"
},
"app.browse.discover-servers": {
"message": "Descoperă servere"
},
"app.browse.hide-added-servers": {
"message": "Ascunde serverele deja adăugate"
},
"app.browse.server.installing": {
"message": "Se instalează"
},
"app.export-modal.export-button": {
"message": "Exportă"
},
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.install.phase.finalizing": {
"message": "Se finalizează"
},
"app.instance.admonitions.shared-instance.added-label": {
"message": "Adăugat"
},
"app.instance.modpack-already-installed.create": {
"message": "Creează"
},
"app.instance.share.sign-in.button": {
"message": "Conectează-te"
},
"app.instance.shared-instance-already-installed.instance": {
"message": "Instanță"
},
"app.instance.worlds.delete-world-modal.delete-button": {
"message": "Șterge lumea"
},
"app.instance.worlds.delete-world-modal.title": {
"message": "Șterge lumea"
},
"app.instance.worlds.delete-world-modal.warning-header": {
"message": "Se șterge {name}"
},
"app.instance.worlds.filter-offline": {
"message": "Offline"
},
"app.instance.worlds.filter-online": {
"message": "Online"
},
"app.instance.worlds.filter-vanilla": {
"message": "Vanilla"
},
"app.instance.worlds.refreshing": {
"message": "Se reîmprospătează..."
},
"app.java-detection.cancel": {
"message": "Anulează"
},
"app.java-detection.columns.actions": {
"message": "Acțiuni"
},
"app.modal.install-to-play.header": {
"message": "Instalați pentru a juca"
},
+31 -7
View File
@@ -1220,6 +1220,9 @@
"app.update.complete-toast.title": {
"message": "Версия {version} успешно установлена!"
},
"app.user.project.install-to-instance": {
"message": "Установить в сборку"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1338,7 +1341,7 @@
"message": "Связанная сборка"
},
"installation-settings.shared-instance.title": {
"message": "Отменить публикацию экземпляра"
"message": "Закрытие доступа"
},
"installation-settings.shared-instance.unlink-button": {
"message": "Отвязать сборку"
@@ -1350,13 +1353,13 @@
"message": "Отвязка..."
},
"installation-settings.shared-instance.unpublish-button": {
"message": "Отменить публикацию общего экземпляра"
"message": "Закрыть доступ к сборке"
},
"installation-settings.shared-instance.unpublish-description": {
"message": "Удалите этот общий экземпляр из Modrinth и прекратите отправлять обновления всем, кто его использует. Ваш локальный экземпляр останется без изменений."
"message": "Удаляет сборку с серверов Modrinth и прекращает отправку обновлений. Локальная копия сборки останется нетронутой."
},
"installation-settings.shared-instance.unpublishing-button": {
"message": "Снятие с публикации..."
"message": "Закрытие доступа..."
},
"installation-settings.unlink-shared-instance.modal.admonition-body": {
"message": "Это повлияет только на ваш локальный экземпляр. Установленный контент останется на этом устройстве, и это никак не отразится на общем экземпляре и других пользователях."
@@ -1368,13 +1371,13 @@
"message": "Отвязка сборки"
},
"installation-settings.unpublish-shared-instance.modal.admonition-body": {
"message": "Это удалит общий экземпляр с серверов Modrinth. Пользователи, использующие его в приложении Modrinth, перестанут получать обновления, но ваш локальный экземпляр и его содержимое останутся на этом устройстве."
"message": "Сборка будет удалена с серверов Modrinth. Локальная копия и её контент останутся на устройстве, а другие участники перестанут получать обновления."
},
"installation-settings.unpublish-shared-instance.modal.admonition-header": {
"message": "Отмена публикации общего экземпляра"
"message": "Закрытие доступа к сборке"
},
"installation-settings.unpublish-shared-instance.modal.header": {
"message": "Отменить публикацию общего экземпляра"
"message": "Закрытие доступа к сборке"
},
"instance.action.create-shortcut": {
"message": "Создать ярлык"
@@ -1610,6 +1613,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Команды запуска игры"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Команды выполняются в рабочем каталоге сборки. Список переменных:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: абсолютный путь к папке сборки"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: имя папки сборки"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: аргументы JVM, передаваемые игре"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: абсолютный путь к Java"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: аналог $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: название сборки"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Обёртка"
},
@@ -218,6 +218,9 @@
"app.auth-servers.unreachable.header": {
"message": "Serveri za autentifikaciju su nedostupni"
},
"app.behavior-settings.confirmations.title": {
"message": "Potvrde"
},
"app.browse.add-servers-to-instance": {
"message": "Dodavanje servera na instancu"
},
+45 -3
View File
@@ -62,6 +62,12 @@
"app.action-bar.install.summary.invalid-modpack-files": {
"message": "Modpaketets filer har ogiltig metadata"
},
"app.action-bar.install.summary.java-setup-failed": {
"message": "Java-installationen kunde inte avslutas"
},
"app.action-bar.install.summary.loader-setup-failed": {
"message": "Konfigurering av loader misslyckades"
},
"app.action-bar.install.summary.local-data-error": {
"message": "Kunde inte uppdatera lokal data"
},
@@ -173,6 +179,9 @@
"app.appearance-settings.show-play-time.title": {
"message": "Visa speltid"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Hoppa över bekräftelser till lågriskåtgärder som att duplicera installationer, vanlig innehålls radering, bulk uppdateringar, avlänkning och reparationer. Varningar för farligare åtgärder visas alltid."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Hoppa över icke nödvändiga varningar"
},
@@ -195,7 +204,7 @@
"message": "Kan ej nå autentiseringsservrarna"
},
"app.behavior-settings.confirmations.title": {
"message": "Bekräftningar"
"message": "Bekräftelser"
},
"app.behavior-settings.content.title": {
"message": "Hem och innehåll"
@@ -228,7 +237,7 @@
"message": "Utforska {projectType}"
},
"app.browse.discover-servers": {
"message": "Upptäck servrar"
"message": "Utforska servrar"
},
"app.browse.hide-added-servers": {
"message": "Göm redan tillagda servrar"
@@ -821,6 +830,9 @@
"app.settings.default-instance-options.java-arguments.title": {
"message": "Java-argument"
},
"app.settings.default-instance-options.memory-allocation.title": {
"message": "Minnestilldelning"
},
"app.settings.default-instance-options.post-exit-hook.description": {
"message": "Körs efter att spelet stängs."
},
@@ -828,7 +840,7 @@
"message": "Körs innan instansen startar."
},
"app.settings.default-instance-options.pre-launch-hook.placeholder": {
"message": "Ange för-starts kommando..."
"message": "Ange för-startskommando..."
},
"app.settings.default-instance-options.width.placeholder": {
"message": "Ange bredd..."
@@ -881,6 +893,9 @@
"app.settings.tabs.behavior": {
"message": "Beteende"
},
"app.settings.tabs.default-instance-options": {
"message": "Standard spelinställningar"
},
"app.settings.tabs.java-installations": {
"message": "Java-installationer"
},
@@ -1094,6 +1109,9 @@
"app.update.complete-toast.title": {
"message": "Version {version} har installerats!"
},
"app.user.project.install-to-instance": {
"message": "Installera till instans"
},
"app.world.server-modal.placeholder-address": {
"message": "exempel.modrinth.gg"
},
@@ -1217,6 +1235,12 @@
"installation-settings.shared-instance.unpublishing-button": {
"message": "Avpublicerar..."
},
"installation-settings.unlink-shared-instance.modal.admonition-header": {
"message": "Avlänkar delad instans"
},
"installation-settings.unlink-shared-instance.modal.header": {
"message": "Avlänka delad instans"
},
"installation-settings.unpublish-shared-instance.modal.admonition-body": {
"message": "Detta raderar den delade instansen från Modrinths servrar. De som använder den i Modrinth App kommer sluta få uppdateringar, men din lokala instans och dess innehåll är kvar på den här enheten."
},
@@ -1316,9 +1340,15 @@
"instance.server-modal.resource-pack": {
"message": "Resurs pack"
},
"instance.settings.sharing.active-invites.actions": {
"message": "Åtgärder"
},
"instance.settings.sharing.active-invites.code": {
"message": "Inbjudningslänk"
},
"instance.settings.sharing.active-invites.empty": {
"message": "Det finns inga aktiva inbjudningar."
},
"instance.settings.sharing.active-invites.expires": {
"message": "Går ut"
},
@@ -1334,6 +1364,12 @@
"instance.settings.sharing.active-invites.uses": {
"message": "Användningar"
},
"instance.settings.sharing.revoke-invite.admonition-body": {
"message": "Inbjudningslänken <monospace>{code}</monospace> kommer sluta fungera omedelbart. De som redan gått med kommer ha kvar åtkomst."
},
"instance.settings.sharing.revoke-invite.admonition-header": {
"message": "Den här åtgärden kan inte ångras"
},
"instance.settings.sharing.revoke-invite.confirm": {
"message": "Återkalla inbjudan"
},
@@ -1448,6 +1484,12 @@
"instance.settings.tabs.hooks.title": {
"message": "Hooks för spelstart"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: Instansmappens namn"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: Instansnamnet"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Omslag"
},
@@ -173,6 +173,9 @@
"app.appearance-settings.hide-nametag.title": {
"message": "İsim etiketini gizle"
},
"app.appearance-settings.jump-back-into-worlds.description": {
"message": "Ana sayfadaki “Tekrar oyuna katıl” bölümünde son oynanan dünyaları göster."
},
"app.appearance-settings.jump-back-into-worlds.title": {
"message": "Dünyalara hızlıca geri dön"
},
@@ -188,9 +191,15 @@
"app.appearance-settings.native-decorations.title": {
"message": "Sistem pencere çerçevesi"
},
"app.appearance-settings.show-play-time.description": {
"message": "Her bir örneği ne kadar süreyle oynadığını göster."
},
"app.appearance-settings.show-play-time.title": {
"message": "Oynama süresini göster"
},
"app.appearance-settings.skip-non-essential-warnings.description": {
"message": "Yinelenen yüklemeler, normal içerik silme, toplu güncellemeler, bağlantıların kaldırılması ve onarımlar gibi düşük riskli işlemlerde onay adımlarını atlayın. Tehlikeli işlemlerle ilgili uyarılar her zaman gösterilir."
},
"app.appearance-settings.skip-non-essential-warnings.title": {
"message": "Gerekli olmayan uyarıları atla"
},
@@ -215,6 +224,12 @@
"app.behavior-settings.confirmations.title": {
"message": "Onaylamalar"
},
"app.behavior-settings.content.title": {
"message": "Ana sayfa ve içerik"
},
"app.behavior-settings.startup-and-navigation.title": {
"message": "Başlatma ve gezinme"
},
"app.browse.add-servers-to-instance": {
"message": "Sunucuyu kuruluma ekle"
},
@@ -245,6 +260,9 @@
"app.browse.hide-added-servers": {
"message": "Zaten eklenmiş sunucuları gizle"
},
"app.browse.hide-installed-modpacks": {
"message": "Zaten yüklenmiş olanları gizle"
},
"app.browse.project-type.modpacks": {
"message": "Mod paketleri"
},
@@ -473,6 +491,9 @@
"app.instance.share.remove-user-modal.effect-invite-again": {
"message": "Onları sonradan tekrar davet edebilirsiniz"
},
"app.instance.share.remove-user-modal.effect-last-user": {
"message": "Bu son kullanıcıdır; bu örnek için paylaşım özelliği devre dışı bırakılacaktır"
},
"app.instance.share.remove-user-modal.effects-label": {
"message": "Ne olur?"
},
@@ -485,12 +506,18 @@
"app.instance.share.remove-user-modal.user-avatar-alt": {
"message": "{username} nın avatarı"
},
"app.instance.share.remove-user-modal.warning-body": {
"message": "{username}'nın bu paylaşılan örneğe erişimini iptal ederseniz, bu kişinin güncellemeleri alabilmesi için onu yeniden davet etmeniz gerekecektir."
},
"app.instance.share.sign-in.button": {
"message": "Giriş yap"
},
"app.instance.share.unable-to-connect.heading": {
"message": "Bağlanılamıyor"
},
"app.instance.share.unlink.body": {
"message": "Örneğini paylaşmak için bu mod paketinin bağlantısını kaldırmalısın"
},
"app.instance.shared-instance-already-installed.install-anyway": {
"message": "Yine de kur"
},
+29 -2
View File
@@ -590,6 +590,9 @@
"app.instance.worlds.no-worlds-heading": {
"message": "Сервера або світи не додано"
},
"app.instance.worlds.refreshing": {
"message": "Перезавантаження…"
},
"app.instance.worlds.remove-server-modal.remove-button": {
"message": "Видалити сервер"
},
@@ -729,10 +732,10 @@
"message": "Уміст спільного профілю"
},
"app.modal.install-to-play.shared-instance-unknown-files-description": {
"message": "Цей спільний профіль містить файли, які не є опубліковані на Modrinth. Ми наполегливо рекомендуємо встановлювати лише ті файли, яким довіряєте."
"message": "Цей спільний профіль містить файли, які не викладені на Modrinth. Наполегливо радимо встановлювати вміст лише з тих джерел, яким ви довіряєте."
},
"app.modal.install-to-play.unknown-files-description": {
"message": "Ця збірка сервера містить файли, які не є опубліковані на Modrinth. Ми наполегливо рекомендуємо встановлювати лише ті файли, яким довіряєте."
"message": "Ця збірка сервера містить файли, які не викладені на Modrinth. Наполегливо радимо встановлювати вміст лише з тих джерел, яким ви довіряєте."
},
"app.modal.install-to-play.unknown-files-warning": {
"message": "Попередження про невідомі файли"
@@ -1226,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "Версію {version} успішно встановлено!"
},
"app.user.project.install-to-instance": {
"message": "Установити в профіль"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1619,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "Гуки запуску гри"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "Гуки виконуються в робочій директорії профілю з наступними змінними:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR: повний шлях до теки профілю"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID: назва теки профілю"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS: надані для гри аргументи JVM"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA: повний шлях до java"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR: те саме, що $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME: назва профілю"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Обгортач"
},
+27 -3
View File
@@ -183,7 +183,7 @@
"message": "当 Minecraft 进程启动时,将 Modrinth App 最小化。"
},
"app.appearance-settings.minimize-launcher.title": {
"message": "最小化启动器"
"message": "最小化 App"
},
"app.appearance-settings.native-decorations.description": {
"message": "使用操作系统的标题栏和窗口控件。需要重启应用。"
@@ -363,7 +363,7 @@
"message": "推送更新"
},
"app.instance.admonitions.shared-instance.publishing-button": {
"message": "正在推送…"
"message": "正在推送…"
},
"app.instance.admonitions.shared-instance.removed-label": {
"message": "移除内容"
@@ -381,7 +381,7 @@
"message": "查看更新"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "正在审查…"
"message": "正在审查…"
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "{name}需要更新。请更新到最新版本以启动游戏。"
@@ -1229,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "版本 {version} 已成功安装!"
},
"app.user.project.install-to-instance": {
"message": "在实例上安装"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1622,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "游戏启动钩子"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "在此实例工作目录中的钩柄遵循以下这些变量:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR:实例文件夹的绝对路径"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID:实例文件夹名称"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS:已为游戏提供的JVM参数"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVA:Java核心文件的绝对路径"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR:的别名是 $INST_DIR"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME:实例名"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "包装器命令"
},
+26 -2
View File
@@ -1089,7 +1089,7 @@
"message": "無"
},
"app.skins.modal.replace-texture-button": {
"message": "取代材質"
"message": "取代紋理"
},
"app.skins.modal.save-skin-button": {
"message": "儲存外觀"
@@ -1098,7 +1098,7 @@
"message": "正在儲存..."
},
"app.skins.modal.texture-section": {
"message": "材質"
"message": "紋理"
},
"app.skins.modal.upload-skin-first-tooltip": {
"message": "請先上傳外觀檔案!"
@@ -1229,6 +1229,9 @@
"app.update.complete-toast.title": {
"message": "版本 {version} 已成功安裝!"
},
"app.user.project.install-to-instance": {
"message": "安裝至實例"
},
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
@@ -1622,6 +1625,27 @@
"instance.settings.tabs.hooks.title": {
"message": "遊戲啟動掛勾"
},
"instance.settings.tabs.hooks.variables.description": {
"message": "掛勾會在實例的工作目錄中執行,並具備以下變數:"
},
"instance.settings.tabs.hooks.variables.inst-dir.description": {
"message": "$INST_DIR:實例資料夾的絕對路徑"
},
"instance.settings.tabs.hooks.variables.inst-id.description": {
"message": "$INST_ID:實例資料夾的名稱"
},
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
"message": "$INST_JAVA_ARGS:提供給遊戲的 JVM 參數"
},
"instance.settings.tabs.hooks.variables.inst-java.description": {
"message": "$INST_JAVAJava 二進位檔案的絕對路徑"
},
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
"message": "$INST_MC_DIR$INST_DIR 的別名"
},
"instance.settings.tabs.hooks.variables.inst-name.description": {
"message": "$INST_NAME:實例的名稱"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "包裝指令"
},
@@ -1385,14 +1385,14 @@ provideContentManager({
project: linkedModpackProject.value,
projectLink: {
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}`,
query: { i: instance.value.id },
query: { i: instancePage.instanceId.value },
},
version: linkedModpackVersion.value ?? undefined,
versionLink:
linkedModpackProject.value && linkedModpackVersion.value
? {
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}/version/${linkedModpackVersion.value.id}`,
query: { i: instance.value.id },
query: { i: instancePage.instanceId.value },
}
: undefined,
owner: linkedModpackOwner.value
@@ -1479,7 +1479,7 @@ provideContentManager({
icon_url: null,
},
projectLink: item.project?.id
? { path: `/project/${item.project.id}`, query: { i: instance.value.id } }
? { path: `/project/${item.project.id}`, query: { i: instancePage.instanceId.value } }
: undefined,
version: item.version ?? {
id: item.file_name,
@@ -1490,7 +1490,7 @@ provideContentManager({
item.project?.id && item.version?.id
? {
path: `/project/${item.project.id}/version/${item.version.id}`,
query: { i: instance.value.id },
query: { i: instancePage.instanceId.value },
}
: undefined,
owner: item.owner
@@ -2,7 +2,8 @@ import Content from './content/index.vue'
import Files from './files/index.vue'
import Index from './layout.vue'
import Logs from './logs/index.vue'
import Screenshots from './screenshots/index.vue'
import Share from './share/index.vue'
import Worlds from './worlds/index.vue'
export { Content, Files, Index, Logs, Share, Worlds }
export { Content, Files, Index, Logs, Screenshots, Share, Worlds }
@@ -100,6 +100,7 @@ import {
EditIcon,
FolderOpenIcon,
GlobeIcon,
ImagesIcon,
PlayIcon,
PlusIcon,
StopCircleIcon,
@@ -142,7 +143,7 @@ import {
isSharedInstanceUnavailableError,
type SharedInstanceUnavailableReason,
} from '@/helpers/install'
import { get_full_path, kill, remove, run } from '@/helpers/instance'
import { get_full_path, kill, refresh_content_updates, remove, run } from '@/helpers/instance'
import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
import type { GameInstance } from '@/helpers/types'
import { createInstanceShortcut, showInstanceInFolder } from '@/helpers/utils.js'
@@ -180,7 +181,7 @@ const showInstancePlayTime = computed(() => themeStore.getFeatureFlag('show_inst
const online = useOnline()
const offline = computed(() => !online.value)
const instanceId = computed(() => String(route.params.id ?? ''))
const instanceId = ref(String(route.params.id ?? ''))
const instanceQuery = useQuery(
computed(() => ({
...instanceDetailQueryOptions(instanceId.value),
@@ -194,6 +195,23 @@ useQuery(
})),
)
const instance = computed(() => instanceQuery.data.value)
useQuery(
computed(() => ({
queryKey: instanceKeys.contentUpdateCheck(instanceId.value),
queryFn: async () => {
const targetInstanceId = instanceId.value
await refresh_content_updates(targetInstanceId)
await queryClient.invalidateQueries({
queryKey: instanceKeys.content(targetInstanceId),
})
return targetInstanceId
},
enabled: !!instanceId.value && !offline.value && instance.value?.install_stage === 'installed',
staleTime: 10 * 60_000,
gcTime: 30 * 60_000,
retry: false,
})),
)
const linkedProjectId = computed(() => instance.value?.link?.project_id ?? '')
const linkedProjectQuery = useQuery(
computed(() => ({
@@ -242,6 +260,7 @@ onBeforeRouteUpdate(async (to, from) => {
try {
await ensureCriticalInstanceData(targetInstanceId)
instanceId.value = targetInstanceId
} catch (error) {
if (isUnmanagedInstanceError(error)) return { path: '/' }
handleError(error)
@@ -431,6 +450,11 @@ const tabs = computed(() => {
href: `${basePath.value}/files`,
icon: FolderOpenIcon,
},
{
label: 'Screenshots',
href: `${basePath.value}/screenshots`,
icon: ImagesIcon,
},
{
label: 'Worlds',
href: `${basePath.value}/worlds`,
@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/vue-query'
import { get_project_v3 } from '@/helpers/cache.js'
import { get as getInstance } from '@/helpers/instance'
import { get as getInstance, list_screenshots } from '@/helpers/instance'
import { loadInstanceContentData } from '@/helpers/instance-content'
import { get_by_instance_id } from '@/helpers/process'
import { refreshWorlds } from '@/helpers/worlds'
@@ -11,6 +11,8 @@ export const instanceKeys = {
detail: (instanceId: string) => [...instanceKeys.all, 'summary', instanceId] as const,
processes: (instanceId: string) => [...instanceKeys.all, 'processes', instanceId] as const,
content: (instanceId: string) => [...instanceKeys.all, 'content', instanceId] as const,
contentUpdateCheck: (instanceId: string) =>
[...instanceKeys.all, 'content-update-check', instanceId] as const,
rootPath: (instanceId: string) => [...instanceKeys.detail(instanceId), 'root-path'] as const,
files: (instanceId: string, path: string) =>
[...instanceKeys.detail(instanceId), 'files', path] as const,
@@ -20,6 +22,8 @@ export const instanceKeys = {
[...instanceKeys.detail(instanceId), 'installed-project-ids', source] as const,
linkedContent: (instanceId: string) => ['linkedModpackContent', instanceId] as const,
worlds: (instanceId: string) => ['worlds', instanceId] as const,
screenshots: (instanceId: string) =>
[...instanceKeys.detail(instanceId), 'screenshots'] as const,
linkedProject: (projectId: string) => ['project', 'v3', projectId] as const,
sharedEligibility: (userId: string | null | undefined) =>
['shared-instance-eligibility', userId] as const,
@@ -77,3 +81,11 @@ export function instanceWorldsQueryOptions(instanceId: string) {
staleTime: 0,
})
}
export function instanceScreenshotsQueryOptions(instanceId: string) {
return queryOptions({
queryKey: instanceKeys.screenshots(instanceId),
queryFn: () => list_screenshots(instanceId),
staleTime: 0,
})
}
@@ -0,0 +1,650 @@
<template>
<ConfirmModal
ref="deleteModal"
:title="formatMessage(messages.deleteTitle)"
:description="
formatMessage(messages.deleteDescription, {
name: screenshotToDelete?.file_name ?? '',
})
"
:proceed-label="formatMessage(commonMessages.deleteLabel)"
:markdown="false"
@proceed="confirmDelete"
/>
<ConfirmModal
ref="bulkDeleteModal"
:title="formatMessage(messages.bulkDeleteTitle)"
:description="
formatMessage(messages.bulkDeleteDescription, {
count: selectedScreenshotNames.size,
})
"
:proceed-label="formatMessage(commonMessages.deleteLabel)"
:markdown="false"
@proceed="deleteSelectedScreenshots"
/>
<ImagePreviewModal ref="previewModal" :items="previewItems">
<template #actions="{ item }">
<IconButton
v-tooltip="formatMessage(messages.copy)"
:label="formatMessage(messages.copy)"
@click="copyScreenshotByFileName(item.id)"
>
<ClipboardCopyIcon />
</IconButton>
<IconButton
v-tooltip="formatMessage(messages.showInFolder)"
:label="formatMessage(messages.showInFolder)"
@click="openScreenshotByFileName(item.id)"
>
<ExternalIcon />
</IconButton>
<IconButton
v-tooltip="formatMessage(commonMessages.deleteLabel)"
:label="formatMessage(commonMessages.deleteLabel)"
@click="requestPreviewDelete(item.id)"
>
<TrashIcon />
</IconButton>
</template>
</ImagePreviewModal>
<ReadyTransition :pending="screenshotsReadyPending">
<EmptyState
v-if="screenshotsError"
type="error"
:heading="formatMessage(messages.errorHeading)"
:description="screenshotsError.message"
>
<template #actions>
<Button type="outlined" @click="screenshotsQuery.refetch()">
{{ formatMessage(commonMessages.retryButton) }}
</Button>
</template>
</EmptyState>
<EmptyState
v-else-if="screenshots.length === 0"
type="no-images"
:heading="formatMessage(messages.emptyHeading)"
:description="formatMessage(messages.emptyDescription)"
/>
<div v-else class="flex flex-col gap-3">
<div
v-for="(group, groupIndex) in groupedScreenshots"
:key="group.label"
class="relative flex flex-col gap-3"
>
<div
class="absolute left-2.5 top-5 w-px bg-surface-5"
:class="groupIndex === groupedScreenshots.length - 1 ? 'bottom-0' : '-bottom-3'"
/>
<div class="relative flex items-center gap-2">
<div class="flex w-5 shrink-0 items-center justify-center">
<CalendarIcon class="size-5" />
</div>
<span class="text-lg font-semibold leading-5 text-contrast">{{ group.label }}</span>
</div>
<div class="grid grid-cols-1 gap-3 pl-7 sm:grid-cols-2 xl:grid-cols-3">
<div
v-for="screenshot in group.screenshots"
:key="screenshot.file_name"
role="button"
tabindex="0"
class="group relative aspect-video cursor-pointer overflow-hidden rounded-xl border border-solid border-surface-5 bg-surface-2 p-0 text-left shadow-sm transition hover:border-brand focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand"
:class="{ '!border-contrast': selectedScreenshotNames.has(screenshot.file_name) }"
:aria-label="
selectionActive
? formatMessage(
selectedScreenshotNames.has(screenshot.file_name)
? messages.deselectScreenshot
: messages.selectScreenshot,
{ name: screenshot.file_name },
)
: screenshot.file_name
"
:aria-pressed="
selectionActive ? selectedScreenshotNames.has(screenshot.file_name) : undefined
"
@click="activateScreenshot(screenshot, $event)"
@keydown="handleScreenshotKeydown($event, screenshot)"
>
<button
type="button"
class="selection-button group/selection absolute right-0.5 top-0 z-[2] flex size-[50px] cursor-pointer items-start justify-center border-0 bg-transparent p-0 pt-4"
:aria-label="
formatMessage(
selectedScreenshotNames.has(screenshot.file_name)
? messages.deselectScreenshot
: messages.selectScreenshot,
{ name: screenshot.file_name },
)
"
:aria-pressed="selectedScreenshotNames.has(screenshot.file_name)"
@click.stop="toggleScreenshotSelection(screenshot.file_name)"
>
<span
class="relative flex size-6 items-center justify-center rounded-full opacity-0 transition-opacity duration-200 ease-out group-hover:opacity-100 group-hover/selection:brightness-125"
:class="
selectedScreenshotNames.has(screenshot.file_name)
? 'border-0 !opacity-100'
: 'border-2 border-solid border-primary bg-transparent'
"
>
<span
v-if="selectedScreenshotNames.has(screenshot.file_name)"
class="absolute inset-0 rounded-full bg-contrast"
/>
<CheckIcon
v-if="selectedScreenshotNames.has(screenshot.file_name)"
class="relative size-4 invert [stroke-width:3]"
/>
</span>
</button>
<div
v-if="!loadedScreenshots.has(screenshot.file_name)"
class="absolute inset-0 animate-pulse bg-surface-3"
/>
<img
:src="screenshot.url"
:alt="screenshot.file_name"
loading="lazy"
class="h-full w-full object-cover transition duration-200 group-hover:scale-[1.02]"
:class="loadedScreenshots.has(screenshot.file_name) ? 'opacity-100' : 'opacity-0'"
@load="markLoaded(screenshot.file_name)"
/>
<div
class="absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t from-surface-1 to-transparent p-3 pt-[120px] text-contrast opacity-0 transition-opacity duration-200 group-hover:opacity-100 group-focus-within:opacity-100"
>
<div class="min-w-0">
<div v-tooltip="screenshot.file_name" class="truncate text-sm font-semibold">
{{ screenshot.file_name }}
</div>
<div class="text-xs text-secondary">
{{ formatScreenshotTime(screenshot.created_at) }}
</div>
</div>
<div
v-if="!selectionActive"
class="flex shrink-0 translate-y-1 gap-1 opacity-0 transition group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:translate-y-0 group-focus-within:opacity-100"
@click.stop
>
<IconButton
v-tooltip="formatMessage(messages.copy)"
:label="formatMessage(messages.copy)"
type="quiet"
class="bg-surface-2 text-contrast hover:bg-surface-3"
@click="copyScreenshot(screenshot)"
>
<ClipboardCopyIcon />
</IconButton>
<IconButton
v-tooltip="formatMessage(messages.showInFolder)"
:label="formatMessage(messages.showInFolder)"
type="quiet"
class="bg-surface-2 text-contrast hover:bg-surface-3"
@click="openScreenshot(screenshot)"
>
<ExternalIcon />
</IconButton>
<IconButton
v-tooltip="formatMessage(commonMessages.deleteLabel)"
:label="formatMessage(commonMessages.deleteLabel)"
type="quiet"
class="bg-surface-2 text-contrast hover:bg-surface-3"
@click="requestDelete(screenshot)"
>
<TrashIcon />
</IconButton>
</div>
</div>
</div>
</div>
</div>
</div>
<FloatingActionBar
:shown="selectionActive"
:aria-label="formatMessage(messages.selectionAriaLabel)"
hide-when-modal-open
>
<div class="flex items-center gap-0.5">
<span class="px-4 py-2.5 text-base font-semibold tabular-nums text-contrast">
{{ formatMessage(messages.selectedCount, { count: selectedScreenshotNames.size }) }}
</span>
<div class="mx-1 h-6 w-px bg-surface-5" />
<Button type="quiet" :disabled="bulkBusy" @click="clearScreenshotSelection">
{{ formatMessage(commonMessages.clearButton) }}
</Button>
</div>
<div class="ml-auto flex items-center gap-0.5">
<Button type="quiet" :disabled="bulkBusy" @click="exportSelectedScreenshots">
<FileArchiveIcon />
<span class="bar-label">{{ formatMessage(messages.exportZip) }}</span>
</Button>
<div class="mx-1 h-6 w-px bg-surface-5" />
<Button
type="quiet"
color="red"
interaction="filled"
:disabled="bulkBusy"
@click="bulkDeleteModal?.show()"
>
<TrashIcon />
<span class="bar-label">{{ formatMessage(commonMessages.deleteLabel) }}</span>
</Button>
</div>
</FloatingActionBar>
</ReadyTransition>
</template>
<script setup lang="ts">
import {
CalendarIcon,
CheckIcon,
ClipboardCopyIcon,
ExternalIcon,
FileArchiveIcon,
TrashIcon,
} from '@modrinth/assets'
import {
Button,
commonMessages,
ConfirmModal,
defineMessages,
EmptyState,
FloatingActionBar,
IconButton,
injectNotificationManager,
ReadyTransition,
useFormatDateTime,
useReadyState,
useVIntl,
} from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { save } from '@tauri-apps/plugin-dialog'
import { readFile } from '@tauri-apps/plugin-fs'
import dayjs from 'dayjs'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import ImagePreviewModal from '@/components/ui/ImagePreviewModal.vue'
import { instance_listener } from '@/helpers/events.js'
import {
delete_screenshot,
export_screenshots,
open_screenshot,
type InstanceScreenshot,
} from '@/helpers/instance'
import { injectInstancePage } from '../instance-context'
import { instanceKeys, instanceScreenshotsQueryOptions } from '../query-options'
const messages = defineMessages({
emptyHeading: {
id: 'app.instance.screenshots.empty-heading',
defaultMessage: 'No screenshots yet',
},
emptyDescription: {
id: 'app.instance.screenshots.empty-description',
defaultMessage: 'Screenshots you take in-game will appear here.',
},
errorHeading: {
id: 'app.instance.screenshots.error-heading',
defaultMessage: 'Failed to load screenshots',
},
copy: {
id: 'app.instance.screenshots.copy',
defaultMessage: 'Copy image',
},
showInFolder: {
id: 'app.instance.screenshots.show-in-folder',
defaultMessage: 'Show in folder',
},
deleteTitle: {
id: 'app.instance.screenshots.delete-title',
defaultMessage: 'Delete screenshot',
},
deleteDescription: {
id: 'app.instance.screenshots.delete-description',
defaultMessage: 'Permanently delete {name}? This action cannot be undone.',
},
deleteSuccess: {
id: 'app.instance.screenshots.delete-success',
defaultMessage: 'Screenshot deleted',
},
copySuccess: {
id: 'app.instance.screenshots.copy-success',
defaultMessage: 'Screenshot copied to clipboard',
},
selectionAriaLabel: {
id: 'app.instance.screenshots.selection.aria-label',
defaultMessage: 'Selected screenshots',
},
selectedCount: {
id: 'app.instance.screenshots.selection.selected-count',
defaultMessage: '{count} selected',
},
selectScreenshot: {
id: 'app.instance.screenshots.selection.select',
defaultMessage: 'Select {name}',
},
deselectScreenshot: {
id: 'app.instance.screenshots.selection.deselect',
defaultMessage: 'Deselect {name}',
},
exportZip: {
id: 'app.instance.screenshots.selection.export-zip',
defaultMessage: 'Export ZIP',
},
zipArchive: {
id: 'app.instance.screenshots.selection.zip-archive',
defaultMessage: 'ZIP archive',
},
exportSuccess: {
id: 'app.instance.screenshots.selection.export-success',
defaultMessage: 'Screenshots exported',
},
bulkDeleteTitle: {
id: 'app.instance.screenshots.selection.delete-title',
defaultMessage: 'Delete selected screenshots',
},
bulkDeleteDescription: {
id: 'app.instance.screenshots.selection.delete-description',
defaultMessage:
'Delete {count, plural, one {# screenshot} other {# screenshots}}? This action cannot be undone.',
},
bulkDeleteSuccess: {
id: 'app.instance.screenshots.selection.delete-success',
defaultMessage: '{count, plural, one {# screenshot deleted} other {# screenshots deleted}}',
},
today: {
id: 'app.instance.screenshots.group.today',
defaultMessage: 'Today',
},
yesterday: {
id: 'app.instance.screenshots.group.yesterday',
defaultMessage: 'Yesterday',
},
thisWeek: {
id: 'app.instance.screenshots.group.this-week',
defaultMessage: 'This week',
},
thisMonth: {
id: 'app.instance.screenshots.group.this-month',
defaultMessage: 'This month',
},
})
type ScreenshotGroup = {
label: string
screenshots: InstanceScreenshot[]
}
const { formatMessage } = useVIntl()
const { addNotification, handleError } = injectNotificationManager()
const instancePage = injectInstancePage()
const queryClient = useQueryClient()
const instanceId = instancePage.instanceId
const previewModal = ref<InstanceType<typeof ImagePreviewModal>>()
const deleteModal = ref<InstanceType<typeof ConfirmModal>>()
const bulkDeleteModal = ref<InstanceType<typeof ConfirmModal>>()
const screenshotToDelete = ref<InstanceScreenshot | null>(null)
const deleteFromPreview = ref(false)
const loadedScreenshots = ref(new Set<string>())
const selectedScreenshotNames = ref(new Set<string>())
const bulkDeleting = ref(false)
const bulkExporting = ref(false)
const screenshotsQuery = useQuery(computed(() => instanceScreenshotsQueryOptions(instanceId.value)))
const screenshotsReadyPending = useReadyState(screenshotsQuery)
const screenshots = computed(() => screenshotsQuery.data.value ?? [])
const selectionActive = computed(() => selectedScreenshotNames.value.size > 0)
const bulkBusy = computed(() => bulkDeleting.value || bulkExporting.value)
const selectedScreenshots = computed(() =>
screenshots.value.filter((screenshot) =>
selectedScreenshotNames.value.has(screenshot.file_name),
),
)
const screenshotsError = computed(() => {
const error = screenshotsQuery.error.value
return error instanceof Error ? error : error ? new Error(String(error)) : null
})
const formatScreenshotDate = useFormatDateTime({ dateStyle: 'long', timeStyle: 'short' })
const formatScreenshotTime = useFormatDateTime({ hour: 'numeric', minute: '2-digit' })
const formatScreenshotMonth = useFormatDateTime({ month: 'long', year: 'numeric' })
const previewItems = computed(() =>
screenshots.value.map((screenshot) => ({
id: screenshot.file_name,
src: screenshot.url,
alt: screenshot.file_name,
title: screenshot.file_name,
description: formatScreenshotDate(screenshot.created_at),
})),
)
const groupedScreenshots = computed((): ScreenshotGroup[] => {
const now = dayjs()
const groups: ScreenshotGroup[] = []
function addToGroup(label: string, screenshot: InstanceScreenshot) {
let group = groups.find((candidate) => candidate.label === label)
if (!group) {
group = { label, screenshots: [] }
groups.push(group)
}
group.screenshots.push(screenshot)
}
for (const screenshot of screenshots.value) {
const created = dayjs(screenshot.created_at)
const isToday = created.isSame(now, 'day')
const isYesterday = created.isSame(now.subtract(1, 'day'), 'day')
if (isToday) {
addToGroup(formatMessage(messages.today), screenshot)
} else if (isYesterday) {
addToGroup(formatMessage(messages.yesterday), screenshot)
} else if (created.isSame(now, 'week')) {
addToGroup(formatMessage(messages.thisWeek), screenshot)
} else if (created.isSame(now, 'month')) {
addToGroup(formatMessage(messages.thisMonth), screenshot)
} else {
addToGroup(formatScreenshotMonth(created.toDate()), screenshot)
}
}
return groups
})
watch(screenshots, (currentScreenshots) => {
const currentNames = new Set(currentScreenshots.map((screenshot) => screenshot.file_name))
selectedScreenshotNames.value = new Set(
[...selectedScreenshotNames.value].filter((fileName) => currentNames.has(fileName)),
)
})
function toggleScreenshotSelection(fileName: string) {
if (bulkBusy.value) return
const nextSelection = new Set(selectedScreenshotNames.value)
if (nextSelection.has(fileName)) {
nextSelection.delete(fileName)
} else {
nextSelection.add(fileName)
}
selectedScreenshotNames.value = nextSelection
}
function activateScreenshot(screenshot: InstanceScreenshot, event?: MouseEvent) {
if (selectionActive.value || event?.shiftKey) {
toggleScreenshotSelection(screenshot.file_name)
} else {
showPreview(screenshot)
}
}
function handleScreenshotKeydown(event: KeyboardEvent, screenshot: InstanceScreenshot) {
if (event.target !== event.currentTarget) return
if (event.key !== 'Enter' && event.key !== ' ') return
event.preventDefault()
activateScreenshot(screenshot)
}
function clearScreenshotSelection() {
if (bulkBusy.value) return
selectedScreenshotNames.value = new Set()
}
function markLoaded(fileName: string) {
loadedScreenshots.value.add(fileName)
}
function showPreview(screenshot: InstanceScreenshot) {
const index = screenshots.value.findIndex((item) => item.file_name === screenshot.file_name)
if (index >= 0) previewModal.value?.show(index)
}
function requestDelete(screenshot: InstanceScreenshot) {
deleteFromPreview.value = false
screenshotToDelete.value = screenshot
deleteModal.value?.show()
}
function screenshotByFileName(fileName: string) {
return screenshots.value.find((screenshot) => screenshot.file_name === fileName)
}
function requestPreviewDelete(fileName: string) {
const screenshot = screenshotByFileName(fileName)
if (!screenshot) return
deleteFromPreview.value = true
screenshotToDelete.value = screenshot
deleteModal.value?.show()
}
async function confirmDelete() {
const screenshot = screenshotToDelete.value
if (!screenshot) return
try {
await delete_screenshot(instanceId.value, screenshot.file_name)
if (deleteFromPreview.value) previewModal.value?.hide()
await queryClient.invalidateQueries({ queryKey: instanceKeys.screenshots(instanceId.value) })
addNotification({ type: 'success', title: formatMessage(messages.deleteSuccess) })
} catch (error) {
handleError(error)
} finally {
screenshotToDelete.value = null
deleteFromPreview.value = false
}
}
async function deleteSelectedScreenshots() {
const selected = selectedScreenshots.value
if (bulkBusy.value || selected.length === 0) return
bulkDeleting.value = true
try {
const results = await Promise.allSettled(
selected.map((screenshot) => delete_screenshot(instanceId.value, screenshot.file_name)),
)
const deletedNames = new Set<string>()
for (const [index, result] of results.entries()) {
if (result.status === 'fulfilled') {
deletedNames.add(selected[index].file_name)
} else {
handleError(result.reason)
}
}
if (deletedNames.size > 0) {
selectedScreenshotNames.value = new Set(
[...selectedScreenshotNames.value].filter((fileName) => !deletedNames.has(fileName)),
)
await queryClient.invalidateQueries({
queryKey: instanceKeys.screenshots(instanceId.value),
})
addNotification({
type: 'success',
title: formatMessage(messages.bulkDeleteSuccess, { count: deletedNames.size }),
})
}
} finally {
bulkDeleting.value = false
}
}
async function exportSelectedScreenshots() {
const selected = selectedScreenshots.value
if (bulkBusy.value || selected.length === 0) return
try {
const instanceName = instancePage.instance.value.name.replace(/[\\/:*?"<>|]/g, '-')
const outputPath = await save({
defaultPath: `${instanceName} screenshots.zip`,
filters: [
{
name: formatMessage(messages.zipArchive),
extensions: ['zip'],
},
],
})
if (!outputPath) return
bulkExporting.value = true
await export_screenshots(
instanceId.value,
selected.map((screenshot) => screenshot.file_name),
outputPath,
)
addNotification({ type: 'success', title: formatMessage(messages.exportSuccess) })
} catch (error) {
handleError(error)
} finally {
bulkExporting.value = false
}
}
function copyScreenshotByFileName(fileName: string) {
const screenshot = screenshotByFileName(fileName)
if (screenshot) void copyScreenshot(screenshot)
}
async function copyScreenshot(screenshot: InstanceScreenshot) {
try {
const png = readFile(screenshot.path).then((bytes) => new Blob([bytes], { type: 'image/png' }))
await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })])
addNotification({ type: 'success', title: formatMessage(messages.copySuccess) })
} catch (error) {
handleError(error)
}
}
async function openScreenshot(screenshot: InstanceScreenshot) {
try {
await open_screenshot(instanceId.value, screenshot.file_name)
} catch (error) {
handleError(error)
}
}
function openScreenshotByFileName(fileName: string) {
const screenshot = screenshotByFileName(fileName)
if (screenshot) void openScreenshot(screenshot)
}
const unlistenInstance = await instance_listener(
(event: { instance_id: string; event: string }) => {
if (event.instance_id !== instanceId.value || event.event !== 'screenshots_updated') return
void queryClient.invalidateQueries({ queryKey: instanceKeys.screenshots(instanceId.value) })
},
)
onBeforeUnmount(() => {
unlistenInstance()
})
</script>
+35 -269
View File
@@ -14,86 +14,29 @@
</span>
</Card>
</div>
<div v-if="expandedGalleryItem" class="expanded-image-modal" @click="hideImage">
<div class="content">
<img
class="image"
:class="{ 'zoomed-in': zoomedIn }"
:src="
expandedGalleryItem.raw_url
? expandedGalleryItem.raw_url
: 'https://cdn.modrinth.com/placeholder-banner.svg'
"
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
@click.stop="() => {}"
/>
<div class="floating" @click.stop="() => {}">
<div class="text">
<h2 v-if="expandedGalleryItem.title">
{{ expandedGalleryItem.title }}
</h2>
<p v-if="expandedGalleryItem.description">
{{ expandedGalleryItem.description }}
</p>
</div>
<div class="controls">
<div class="buttons">
<IconButton label="Close" class="close" @click="hideImage">
<XIcon aria-hidden="true" />
</IconButton>
<ButtonLink
class="open btn icon-only !w-9 !px-0 !rounded-full"
target="_blank"
:href="
expandedGalleryItem.raw_url
? expandedGalleryItem.raw_url
: 'https://cdn.modrinth.com/placeholder-banner.svg'
"
>
<ExternalIcon aria-hidden="true" />
</ButtonLink>
<IconButton label="Toggle zoom" @click="zoomedIn = !zoomedIn">
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
<ContractIcon v-else aria-hidden="true" />
</IconButton>
<IconButton
v-if="filteredGallery.length > 1"
label="Previous image"
class="previous"
@click="previousImage()"
>
<LeftArrowIcon aria-hidden="true" />
</IconButton>
<IconButton
v-if="filteredGallery.length > 1"
label="Next image"
class="next"
@click="nextImage()"
>
<RightArrowIcon aria-hidden="true" />
</IconButton>
</div>
</div>
</div>
</div>
</div>
<ImagePreviewModal
ref="imagePreviewModal"
:items="previewItems"
@navigate="trackPreviewNavigation"
>
<template #actions="{ item }">
<ButtonLink
class="open btn icon-only !w-9 !rounded-full !px-0"
target="_blank"
:href="item.src"
>
<ExternalIcon aria-hidden="true" />
</ButtonLink>
</template>
</ImagePreviewModal>
</template>
<script setup>
import {
CalendarIcon,
ContractIcon,
ExpandIcon,
ExternalIcon,
LeftArrowIcon,
RightArrowIcon,
XIcon,
} from '@modrinth/assets'
import { ButtonLink, Card, IconButton, useFormatDateTime } from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { CalendarIcon, ExternalIcon } from '@modrinth/assets'
import { ButtonLink, Card, useFormatDateTime } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import ImagePreviewModal from '@/components/ui/ImagePreviewModal.vue'
import { trackEvent } from '@/helpers/analytics'
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
@@ -115,44 +58,19 @@ const filteredGallery = computed(
() => props.project.gallery?.filter((img) => img.title !== MC_SERVER_BANNER_NAME) ?? [],
)
const expandedGalleryItem = ref(null)
const expandedGalleryIndex = ref(0)
const zoomedIn = ref(false)
const hideImage = () => {
expandedGalleryItem.value = null
show_ads_window()
}
const nextImage = () => {
expandedGalleryIndex.value++
if (expandedGalleryIndex.value >= filteredGallery.value.length) {
expandedGalleryIndex.value = 0
}
expandedGalleryItem.value = filteredGallery.value[expandedGalleryIndex.value]
trackEvent('GalleryImageNext', {
project_id: props.project.id,
url: expandedGalleryItem.value.url,
})
}
const previousImage = () => {
expandedGalleryIndex.value--
if (expandedGalleryIndex.value < 0) {
expandedGalleryIndex.value = filteredGallery.value.length - 1
}
expandedGalleryItem.value = filteredGallery.value[expandedGalleryIndex.value]
trackEvent('GalleryImagePrevious', {
project_id: props.project.id,
url: expandedGalleryItem.value,
})
}
const imagePreviewModal = ref()
const previewItems = computed(() =>
filteredGallery.value.map((item) => ({
id: item.url,
src: item.raw_url || 'https://cdn.modrinth.com/placeholder-banner.svg',
alt: item.title || 'gallery-image',
title: item.title,
description: item.description,
})),
)
const expandImage = (item, index) => {
hide_ads_window()
expandedGalleryItem.value = item
expandedGalleryIndex.value = index
zoomedIn.value = false
imagePreviewModal.value?.show(index)
trackEvent('GalleryImageExpand', {
project_id: props.project.id,
@@ -160,28 +78,12 @@ const expandImage = (item, index) => {
})
}
function keyListener(e) {
if (expandedGalleryItem.value) {
if (e.key === 'Escape') {
e.preventDefault()
hideImage()
} else if (e.key === 'ArrowLeft') {
e.preventDefault()
previousImage()
} else if (e.key === 'ArrowRight') {
e.preventDefault()
nextImage()
}
}
function trackPreviewNavigation(_item, index, direction) {
trackEvent(direction === 'next' ? 'GalleryImageNext' : 'GalleryImagePrevious', {
project_id: props.project.id,
url: filteredGallery.value[index]?.url,
})
}
onMounted(() => {
document.addEventListener('keydown', keyListener)
})
onUnmounted(() => {
document.removeEventListener('keydown', keyListener)
})
</script>
<style scoped lang="scss">
@@ -216,140 +118,4 @@ onUnmounted(() => {
vertical-align: center;
}
}
.expanded-image-modal {
position: fixed;
z-index: 11;
overflow: auto;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000000;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
.content {
position: relative;
width: calc(100vw - 2 * var(--gap-lg));
height: calc(100vh - 2 * var(--gap-lg));
.circle-button {
padding: 0.5rem;
line-height: 1;
display: flex;
max-width: 2rem;
color: var(--color-button-text);
background-color: var(--color-button-bg);
border-radius: var(--size-rounded-max);
margin: 0;
box-shadow: inset 0px -1px 1px rgb(17 24 39 / 10%);
&:not(:last-child) {
margin-right: 0.5rem;
}
&:hover {
background-color: var(--color-button-bg-hover) !important;
svg {
color: var(--color-button-text-hover) !important;
}
}
&:active {
background-color: var(--color-button-bg-active) !important;
svg {
color: var(--color-button-text-active) !important;
}
}
svg {
height: 1rem;
width: 1rem;
}
}
.image {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-width: calc(100vw - 2 * var(--gap-lg));
max-height: calc(100vh - 2 * var(--gap-lg));
border-radius: var(--radius-lg);
&.zoomed-in {
object-fit: cover;
width: auto;
height: calc(100vh - 2 * var(--gap-lg));
max-width: calc(100vw - 2 * var(--gap-lg));
}
}
.floating {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: var(--gap-md);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--gap-md);
transition: opacity 0.25s ease-in-out;
opacity: 1;
padding: 2rem 2rem 0 2rem;
&:not(&:hover) {
opacity: 0.4;
.text {
transform: translateY(2.5rem) scale(0.8);
opacity: 0;
}
.controls {
transform: translateY(0.25rem) scale(0.9);
}
}
.text {
display: flex;
flex-direction: column;
max-width: 40rem;
transition:
opacity 0.25s ease-in-out,
transform 0.25s ease-in-out;
text-shadow: 1px 1px 10px #000000d4;
margin-bottom: 0.25rem;
gap: 0.5rem;
h2 {
color: var(--dark-color-base);
font-size: 1.25rem;
text-align: center;
margin: 0;
}
p {
color: var(--dark-color-base);
margin: 0;
}
}
.controls {
background-color: var(--color-raised-bg);
padding: var(--gap-md);
border-radius: var(--radius-md);
transition:
opacity 0.25s ease-in-out,
transform 0.25s ease-in-out;
}
}
}
}
.buttons {
display: flex;
gap: 0.5rem;
}
</style>
@@ -14,8 +14,13 @@
:members="members"
:dependency-link-creator="createDependencyLink"
>
<template #headerActions>
<template #headerActions="{ primaryFile }">
<Button
v-tooltip="
primaryFile?.url
? primaryFile.filename + ' (' + formatBytes(primaryFile.size) + ')'
: undefined
"
type="colored"
color="brand"
:disabled="installing || (installed && installedVersion === version.id)"
@@ -92,6 +97,7 @@ import {
commonMessages,
defineMessages,
type DependencyContext,
useFormatBytes,
useVIntl,
VersionPage,
} from '@modrinth/ui'
@@ -103,6 +109,7 @@ import { get_project_many, get_version_many } from '@/helpers/cache.js'
import { useBreadcrumb } from '@/providers/breadcrumbs'
const { formatMessage } = useVIntl()
const formatBytes = useFormatBytes()
const messages = defineMessages({
allVersions: {
+4
View File
@@ -1,3 +1,4 @@
import type { AbstractModrinthClient } from '@modrinth/api-client'
import type { AbstractPopupNotificationManager, AbstractWebNotificationManager } from '@modrinth/ui'
import { setupCreationModal } from './setup/creation-modal'
@@ -5,11 +6,14 @@ import { setupFileDropProvider } from './setup/file-drop'
import { setupFilePickerProvider } from './setup/file-picker'
import { setupInstanceImportProvider } from './setup/instance-import'
import { setupTagsProvider } from './setup/tags'
import { setupUserCountryProvider } from './setup/user-country'
export function setupProviders(
client: AbstractModrinthClient,
notificationManager: AbstractWebNotificationManager,
_popupNotificationManager: AbstractPopupNotificationManager,
) {
setupUserCountryProvider(client)
setupTagsProvider(notificationManager)
setupFileDropProvider()
setupFilePickerProvider()
@@ -0,0 +1,16 @@
import type { AbstractModrinthClient } from '@modrinth/api-client'
import { provideUserCountry } from '@modrinth/ui'
import { ref } from 'vue'
export function setupUserCountryProvider(client: AbstractModrinthClient) {
const country = ref('US')
void client.labrinth.geoip
.getCountry()
.then((detectedCountry) => {
country.value = detectedCountry ?? country.value
})
.catch(() => {})
return provideUserCountry(country)
}
+5
View File
@@ -147,6 +147,11 @@ export default new createRouter({
name: 'InstanceWorlds',
component: Instance.Worlds,
},
{
path: 'screenshots',
name: 'InstanceScreenshots',
component: Instance.Screenshots,
},
{
path: 'share',
name: 'InstanceShare',
+6 -1
View File
@@ -200,12 +200,17 @@ fn main() {
"instance_get_install_candidates",
"instance_content",
"instance_get_content_items",
"instance_refresh_content_updates",
"instance_get_dependencies_as_content_items",
"instance_get_linked_modpack_info",
"instance_get_linked_modpack_content",
"instance_get_optimal_jre_key",
"instance_get_full_path",
"instance_get_mod_full_path",
"instance_list_screenshots",
"instance_delete_screenshot",
"instance_export_screenshots",
"instance_open_screenshot",
"instance_list",
"instance_check_installed",
"instance_update_all",
@@ -317,7 +322,7 @@ fn main() {
.commands(&[
"init_ads_window",
"hide_ads_window",
"show_ads_window",
"update_ads_window_hold",
"show_ads_consent_ui",
"expand_ads_consent_webview",
"open_ads_consent_preferences",
+107 -142
View File
@@ -3,14 +3,14 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use tauri::plugin::TauriPlugin;
use tauri::{Emitter, Manager, PhysicalPosition, PhysicalSize, Rect, Runtime};
use tauri::{Emitter, Manager, PhysicalPosition, PhysicalSize, Runtime};
use tauri_plugin_opener::OpenerExt;
use theseus::settings;
use tokio::sync::RwLock;
pub struct AdsState {
pub shown: bool,
pub modal_shown: bool,
pub visibility_holds: usize,
pub consent_required: bool,
pub consent_notification_enabled: bool,
pub consent_overlay_shown: bool,
@@ -24,6 +24,11 @@ const ADS_CONSENT_REQUIRED_EVENT: &str = "ads-consent-required";
const APP_TITLE_BAR_HEIGHT: f32 = 48.0;
#[cfg(any(windows, target_os = "macos"))]
pub(super) const OCCLUDED_AREA_THRESHOLD: f64 = 0.5;
fn should_show_ads_webview(state: &AdsState) -> bool {
state.shown && (state.visibility_holds == 0 || state.consent_overlay_shown)
}
#[cfg(not(target_os = "linux"))]
const ADS_USER_AGENT: &str = concat!(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ",
@@ -126,28 +131,6 @@ fn set_webview_visible<R: Runtime>(webview: &tauri::Webview<R>, visible: bool) {
}
}
fn set_webview_visible_for_window<R: Runtime>(
app: &tauri::AppHandle<R>,
webview: &tauri::Webview<R>,
visible: bool,
) {
let is_minimized = app
.get_window("main")
.and_then(|window| window.is_minimized().ok())
.unwrap_or(false);
let (is_occluded, consent_overlay_shown) = app
.state::<RwLock<AdsState>>()
.try_read()
.map(|state| (state.occluded, state.consent_overlay_shown))
.unwrap_or((false, false));
set_webview_visible(
webview,
visible && !is_minimized && (!is_occluded || consent_overlay_shown),
);
}
#[cfg(any(windows, target_os = "macos"))]
fn compute_ads_webview_occlusion<R: Runtime>(
app: &tauri::AppHandle<R>,
@@ -203,12 +186,20 @@ async fn sync_ads_occlusion<R: Runtime>(app: &tauri::AppHandle<R>) {
}
state.occluded = occluded;
let visible =
state.shown && (!state.modal_shown || state.consent_overlay_shown);
let visible = should_show_ads_webview(&state);
let consent_overlay_shown = state.consent_overlay_shown;
drop(state);
if let Some(webview) = app.webviews().get("ads-window") {
set_webview_visible_for_window(app, webview, visible);
let is_minimized = app
.get_window("main")
.and_then(|window| window.is_minimized().ok())
.unwrap_or(false);
set_webview_visible(
webview,
visible && !is_minimized && (!occluded || consent_overlay_shown),
);
}
}
@@ -225,8 +216,7 @@ fn sync_webview_visibility_for_main_window<R: Runtime>(
} else {
match app.state::<RwLock<AdsState>>().try_read() {
Ok(state) => Some((
state.shown
&& (!state.modal_shown || state.consent_overlay_shown)
should_show_ads_webview(&state)
&& (!state.occluded || state.consent_overlay_shown),
state.consent_overlay_shown,
)),
@@ -277,7 +267,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.setup(|app, _api| {
app.manage(RwLock::new(AdsState {
shown: true,
modal_shown: false,
visibility_holds: 0,
consent_required: false,
consent_notification_enabled: false,
consent_overlay_shown: false,
@@ -297,7 +287,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.try_read()
.map(|state| {
state.shown
&& !state.modal_shown
&& state.visibility_holds == 0
&& !state.consent_required
&& !state.consent_overlay_shown
&& !state.occluded
@@ -362,7 +352,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.invoke_handler(tauri::generate_handler![
init_ads_window,
hide_ads_window,
show_ads_window,
update_ads_window_hold,
show_ads_consent_ui,
expand_ads_consent_webview,
open_ads_consent_preferences,
@@ -431,6 +421,46 @@ fn get_device_pixel_ratio<R: Runtime>(
})
}
fn sync_ads_webview_visibility<R: Runtime>(
app: &tauri::AppHandle<R>,
state: &AdsState,
dpr: f32,
) -> crate::api::Result<()> {
let webviews = app.webviews();
let Some(webview) = webviews.get("ads-window") else {
return Ok(());
};
if should_show_ads_webview(state) {
let (position, size) = if state.consent_overlay_shown {
get_overlay_webview_position(app)?
} else {
get_webview_position(app, dpr)?
};
let is_minimized = app
.get_window("main")
.and_then(|window| window.is_minimized().ok())
.unwrap_or(false);
webview.set_size(size).ok();
webview.set_position(position).ok();
webview.show().ok();
set_webview_visible(
webview,
!is_minimized && (!state.occluded || state.consent_overlay_shown),
);
} else {
webview
.set_position(PhysicalPosition::new(-1000, -1000))
.ok();
webview.hide().ok();
set_webview_visible(webview, false);
}
Ok(())
}
#[tauri::command]
#[cfg(not(target_os = "linux"))]
pub async fn init_ads_window<R: Runtime>(
@@ -447,10 +477,6 @@ pub async fn init_ads_window<R: Runtime>(
state.shown = true;
}
if state.modal_shown && !state.consent_overlay_shown {
return Ok(());
}
let layout = if state.consent_overlay_shown {
get_overlay_webview_position(&app)
} else {
@@ -459,21 +485,7 @@ pub async fn init_ads_window<R: Runtime>(
if let Ok((position, size)) = layout {
let webview = if let Some(webview) = app.webviews().get("ads-window") {
// set both the `hide`/`show` state and `position`,
// to ensure that the webview is actually shown/hidden
if state.shown {
webview.show().ok();
webview.set_position(position).ok();
webview.set_size(size).ok();
set_webview_visible_for_window(&app, webview, true);
} else {
webview.hide().ok();
webview
.set_position(PhysicalPosition::new(-1000, -1000))
.ok();
set_webview_visible(webview, false);
}
sync_ads_webview_visibility(&app, &state, dpr)?;
Some(webview.clone())
} else if let Some(window) = app.get_window("main") {
let ads_consent_script = [
@@ -510,7 +522,7 @@ pub async fn init_ads_window<R: Runtime>(
}),
// set both the `hide`/`show` state and `position`,
// to ensure that the webview is actually shown/hidden
if state.shown {
if should_show_ads_webview(&state) {
position
} else {
PhysicalPosition::new(-1000.0, -1000.0)
@@ -518,13 +530,7 @@ pub async fn init_ads_window<R: Runtime>(
size,
)?;
if state.shown {
webview.show().ok();
set_webview_visible_for_window(&app, &webview, true);
} else {
webview.hide().ok();
set_webview_visible(&webview, false);
}
sync_ads_webview_visibility(&app, &state, dpr)?;
webview.with_webview(#[allow(unused_variables)] |webview2| {
#[cfg(windows)]
@@ -672,38 +678,32 @@ pub async fn init_ads_window<R: Runtime>(
pub async fn init_ads_window() {}
#[tauri::command]
pub async fn show_ads_window<R: Runtime>(
pub async fn update_ads_window_hold<R: Runtime>(
app: tauri::AppHandle<R>,
acquire: bool,
dpr: f32,
) -> crate::api::Result<()> {
let mut show_consent_notification = false;
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
if let Some(webview) = app.webviews().get("ads-window") {
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
state.modal_shown = false;
if state.shown {
let (position, size) = if state.consent_overlay_shown {
get_overlay_webview_position(&app)?
} else {
get_webview_position(&app, dpr)?
};
// set both the `hide`/`show` state and `position`,
// to ensure that the webview is actually shown/hidden
webview.set_size(size).ok();
webview.set_position(position).ok();
webview.show().ok();
set_webview_visible_for_window(&app, webview, true);
}
show_consent_notification = state.shown
&& state.consent_required
&& state.consent_notification_enabled;
if acquire {
state.visibility_holds = state.visibility_holds.saturating_add(1);
} else if state.visibility_holds > 0 {
state.visibility_holds -= 1;
} else {
tracing::warn!(
"Attempted to release an ads window hold when none were active"
);
}
if show_consent_notification {
sync_ads_webview_visibility(&app, &state, dpr)?;
if !acquire
&& state.visibility_holds == 0
&& state.shown
&& state.consent_required
&& state.consent_notification_enabled
{
app.emit_to("main", ADS_CONSENT_REQUIRED_EVENT, true).ok();
}
@@ -722,29 +722,11 @@ pub async fn hide_ads_window<R: Runtime>(
if reset {
state.shown = false;
state.consent_overlay_shown = false;
}
if let Some(webview) = app.webviews().get("ads-window") {
if !reset {
state.modal_shown = true;
if state.consent_overlay_shown {
let (position, size) = get_overlay_webview_position(&app)?;
webview.set_size(size).ok();
webview.set_position(position).ok();
webview.show().ok();
set_webview_visible_for_window(&app, webview, true);
return Ok(());
}
}
// set both the `hide`/`show` state and `position`,
// to ensure that the webview is actually shown/hidden
webview
.set_position(PhysicalPosition::new(-1000, -1000))
.ok();
webview.hide().ok();
sync_ads_webview_visibility(
&app,
&state,
get_device_pixel_ratio(&app, None),
)?;
}
if reset {
@@ -761,7 +743,7 @@ pub async fn show_ads_consent_ui<R: Runtime>(
) -> crate::api::Result<()> {
let mut show_notification = false;
if let Some(webview) = app.webviews().get("ads-window") {
if app.webviews().contains_key("ads-window") {
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
@@ -772,14 +754,11 @@ pub async fn show_ads_consent_ui<R: Runtime>(
state.consent_overlay_shown = false;
show_notification = state.shown && notification_enabled;
if state.shown && !state.modal_shown {
let dpr = get_device_pixel_ratio(&app, None);
let (position, size) = get_webview_position(&app, dpr)?;
webview.set_size(size).ok();
webview.set_position(position).ok();
webview.show().ok();
set_webview_visible_for_window(&app, webview, true);
}
sync_ads_webview_visibility(
&app,
&state,
get_device_pixel_ratio(&app, None),
)?;
}
app.emit_to("main", ADS_CONSENT_REQUIRED_EVENT, show_notification)
@@ -792,7 +771,7 @@ pub async fn show_ads_consent_ui<R: Runtime>(
pub async fn expand_ads_consent_webview<R: Runtime>(
app: tauri::AppHandle<R>,
) -> crate::api::Result<()> {
if let Some(webview) = app.webviews().get("ads-window") {
if app.webviews().contains_key("ads-window") {
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
@@ -801,16 +780,11 @@ pub async fn expand_ads_consent_webview<R: Runtime>(
}
state.consent_overlay_shown = true;
let (position, size) = get_overlay_webview_position(&app)?;
webview
.set_bounds(Rect {
position: position.into(),
size: size.into(),
})
.ok();
webview.show().ok();
set_webview_visible_for_window(&app, webview, true);
sync_ads_webview_visibility(
&app,
&state,
get_device_pixel_ratio(&app, None),
)?;
}
Ok(())
@@ -854,20 +828,11 @@ pub async fn finish_ads_consent_flow<R: Runtime>(
state.consent_notification_enabled = false;
state.consent_overlay_shown = false;
if state.shown && !state.modal_shown {
let dpr = get_device_pixel_ratio(&app, dpr);
let (position, size) = get_webview_position(&app, dpr)?;
webview.set_size(size).ok();
webview.set_position(position).ok();
webview.show().ok();
set_webview_visible_for_window(&app, webview, true);
} else {
webview
.set_position(PhysicalPosition::new(-1000, -1000))
.ok();
webview.hide().ok();
}
sync_ads_webview_visibility(
&app,
&state,
get_device_pixel_ratio(&app, dpr),
)?;
drop(state);
+87
View File
@@ -4,6 +4,9 @@ use path_util::SafeRelativeUtf8UnixPathBuf;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Runtime};
use tauri_plugin_fs::FsExt;
use tauri_plugin_opener::OpenerExt;
use theseus::DownloadReason;
use theseus::data::{
AppliedContentSetPatch, ContentItem, Dependency,
@@ -30,12 +33,17 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
instance_get_install_candidates,
instance_content,
instance_get_content_items,
instance_refresh_content_updates,
instance_get_dependencies_as_content_items,
instance_get_linked_modpack_info,
instance_get_linked_modpack_content,
instance_get_optimal_jre_key,
instance_get_full_path,
instance_get_mod_full_path,
instance_list_screenshots,
instance_delete_screenshot,
instance_export_screenshots,
instance_open_screenshot,
instance_check_installed,
instance_update_all,
instance_update_project,
@@ -69,6 +77,14 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
.build()
}
#[derive(Serialize, Debug, Clone)]
pub struct InstanceScreenshot {
pub file_name: String,
pub created_at: chrono::DateTime<chrono::Utc>,
pub path: PathBuf,
pub url: url::Url,
}
#[derive(Serialize, Debug, Clone)]
pub struct Instance {
pub id: String,
@@ -524,6 +540,11 @@ pub async fn instance_get_content_items(
)
}
#[tauri::command]
pub async fn instance_refresh_content_updates(instance_id: &str) -> Result<()> {
Ok(theseus::instance::refresh_content_updates(instance_id).await?)
}
#[tauri::command]
pub async fn instance_get_dependencies_as_content_items(
dependencies: Vec<Dependency>,
@@ -575,6 +596,72 @@ pub async fn instance_get_mod_full_path(
Ok(theseus::instance::get_mod_full_path(instance_id, project_path).await?)
}
#[tauri::command]
pub async fn instance_list_screenshots<R: Runtime>(
app_handle: AppHandle<R>,
instance_id: &str,
) -> Result<Vec<InstanceScreenshot>> {
let screenshots = theseus::instance::list_screenshots(instance_id).await?;
let mut result = Vec::with_capacity(screenshots.len());
for screenshot in screenshots {
app_handle
.asset_protocol_scope()
.allow_file(&screenshot.path)
.map_err(|error| std::io::Error::other(error.to_string()))?;
app_handle
.fs_scope()
.allow_file(&screenshot.path)
.map_err(|error| std::io::Error::other(error.to_string()))?;
let url = super::utils::tauri_convert_file_src(&screenshot.path)?;
result.push(InstanceScreenshot {
file_name: screenshot.file_name,
created_at: screenshot.created_at,
path: screenshot.path,
url,
});
}
Ok(result)
}
#[tauri::command]
pub async fn instance_delete_screenshot(
instance_id: &str,
file_name: &str,
) -> Result<()> {
Ok(theseus::instance::delete_screenshot(instance_id, file_name).await?)
}
#[tauri::command]
pub async fn instance_export_screenshots(
instance_id: &str,
file_names: Vec<String>,
export_path: PathBuf,
) -> Result<()> {
Ok(theseus::instance::export_screenshots(
instance_id,
&file_names,
export_path,
)
.await?)
}
#[tauri::command]
pub async fn instance_open_screenshot<R: Runtime>(
app_handle: AppHandle<R>,
instance_id: &str,
file_name: &str,
) -> Result<()> {
let path =
theseus::instance::get_screenshot_path(instance_id, file_name).await?;
app_handle
.opener()
.reveal_item_in_dir(path)
.map_err(|error| std::io::Error::other(error.to_string()))?;
Ok(())
}
#[tauri::command]
pub async fn instance_get_optimal_jre_key(
instance_id: &str,
+475 -285
View File
@@ -414,6 +414,35 @@ components:
- unknown
- signature
example: required-resource-pack
EnvironmentEnum:
type: string
enum:
- client_and_server
- client_only
- client_only_server_optional
- singleplayer_only
- server_only
- server_only_client_optional
- dedicated_server_only
- client_or_server
- client_or_server_prefers_both
- unknown
example: client_and_server
description: 'The environment a project or version supports. For an explanation of each environment, see the blog post here: https://modrinth.com/news/article/new-environments/#new-system'
EnvironmentInputEnum:
type: string
enum:
- client_and_server
- client_only
- client_only_server_optional
- singleplayer_only
- server_only
- server_only_client_optional
- dedicated_server_only
- client_or_server
- client_or_server_prefers_both
example: client_and_server
description: 'The environment a version supports. For an explanation of each environment, see the blog post here: https://modrinth.com/news/article/new-environments/#new-system'
# https://github.com/modrinth/code/blob/main/apps/labrinth/src/routes/v2/version_creation.rs#L32-L76
CreatableVersion:
allOf:
@@ -433,19 +462,7 @@ components:
type: string
description: The multipart field name of the primary file
environment:
type: string
enum:
- client_and_server
- client_only
- client_only_server_optional
- singleplayer_only
- server_only
- server_only_client_optional
- dedicated_server_only
- client_or_server
- client_or_server_prefers_both
- unknown
description: The environment that this version is for.
$ref: '#/components/schemas/EnvironmentInputEnum'
file_types:
type: object
additionalProperties:
@@ -496,6 +513,8 @@ components:
deprecated: true
example: null
nullable: true
environment:
$ref: '#/components/schemas/EnvironmentEnum'
files:
type: array
items:
@@ -514,6 +533,7 @@ components:
- version_type
- loaders
- featured
- environment
VersionFile:
type: object
properties:
@@ -627,9 +647,160 @@ components:
required:
- loaders
- game_versions
# Project
# Fields that can be used in everything. Search, direct project lookup, project editing, you name it.
BaseProject:
# Search hit from GET /search
ProjectResult:
type: object
properties:
project_id:
type: string
description: The ID of the project, encoded as a base62 string
example: AABBCCDD
project_type:
type: string
enum: [mod, modpack, resourcepack, shader]
description: The project type of the project
example: mod
all_project_types:
type: array
items:
type: string
enum: [mod, resourcepack, datapack, shader, modpack, plugin]
description: All project types across every version of the project, unlike `project_type` which only reflects a version-specific type
example: [mod, plugin, datapack]
title:
type: string
description: The title or name of the project
description:
type: string
description: A short sentence summarizing the project, no more than a sentence or two.
author:
type: string
description: The username of the project's author
example: my_user
categories:
type: array
items:
type: string
example: [technology, adventure, fabric]
description: A list of the featured categories that the project has.
display_categories:
type: array
items:
type: string
description: A list of the featured categories that the project has. Equivalent to `categories` on the project itself.
example: ['technology', 'fabric']
versions:
type: array
items:
type: string
description: A list of the minecraft versions supported by the project
example: ['1.8', '1.8.9']
downloads:
type: integer
description: The total number of downloads of the project
follows:
type: integer
description: The total number of users following the project
icon_url:
type: string
example: https://cdn.modrinth.com/data/AABBCCDD/b46513nd83hb4792a9a0e1fn28fgi6090c1842639.png
description: The URL of the project's icon
date_created:
type: string
format: ISO-8601
description: The date the project was created
date_modified:
type: string
format: ISO-8601
description: The date the latest version of the project was created
latest_version:
type: string
description: The ID of the latest version of the project
example: IIJJKKLL
license:
type: string
description: The SPDX license ID of a project
example: MIT
environment:
type: array
items:
$ref: '#/components/schemas/EnvironmentEnum'
description: 'All the environments that versions of this project support. Not in any particular order, we recommend using the environment information on a version instead. For an explanation of each environment, see the blog post here: https://modrinth.com/news/article/new-environments/#new-system'
example: [client_and_server]
gallery:
type: array
description: A list of images that have been uploaded to the project's gallery
example:
[
'https://cdn.modrinth.com/data/AABBCCDD/images/009b7d8d6e8bf04968a29421117c59b3efe2351a.png',
'https://cdn.modrinth.com/data/AABBCCDD/images/c21776867afb6046fdc3c21dbcf5cc50ae27a236.png',
]
items:
type: string
slug:
type: string
description: "The slug of a project, used for vanity URLs. Regex: ```^[\\w!@$()`.+,\"\\-']{3,64}$```"
example: my_project
nullable: true
author_id:
type: string
description: The ID of the project's author
example: EEFFGGHH
nullable: true
organization:
type: string
description: The name of the organization that owns this project
example: my_org
nullable: true
organization_id:
type: string
description: The ID of the organization that owns this project
example: AABBCCDD
nullable: true
featured_gallery:
type: string
description: The featured gallery image of the project
nullable: true
color:
type: integer
example: 8703084
description: The RGB color of the project, automatically generated from the project icon
nullable: true
client_side:
type: string
enum: [required, optional, unsupported, unknown]
description: Deprecated - use `environment` instead. The client side support of the project
example: required
deprecated: true
server_side:
type: string
enum: [required, optional, unsupported, unknown]
description: Deprecated - use `environment` instead. The server side support of the project
example: optional
deprecated: true
required:
- project_id
- project_type
- all_project_types
- title
- description
- author
- categories
- display_categories
- versions
- downloads
- follows
- icon_url
- date_created
- date_modified
- latest_version
- license
- environment
- gallery
- client_side
- server_side
# Fields shared by create/edit project request bodies
NonSearchProject:
type: object
properties:
slug:
@@ -639,203 +810,84 @@ components:
title:
type: string
description: The title or name of the project
example: My Project
description:
type: string
description: A short description of the project
example: A short description
description: A short sentence summarizing the project, no more than a sentence or two.
body:
type: string
description: A long form description of the project
example: A long body describing my project in detail
categories:
type: array
items:
type: string
example: [technology, adventure, fabric]
description: A list of the categories that the project has
description: A list of the featured categories that the project has.
additional_categories:
type: array
items:
type: string
description: A list of additional categories that the project also has. These are supplementary to the featured categories, and does not include them again.
example: [technology, adventure, fabric]
status:
type: string
enum:
[
approved,
archived,
rejected,
draft,
unlisted,
processing,
withheld,
scheduled,
private,
unknown,
]
description: The status of the project
example: approved
requested_status:
type: string
enum: [approved, archived, unlisted, private, draft]
description: The requested status when submitting for review or scheduling the project for release. Approved status refers to "Public" visibility.
nullable: true
issues_url:
type: string
description: An optional link to where to submit bugs or issues with the project
example: https://github.com/modrinth/code/issues
nullable: true
source_url:
type: string
description: An optional link to the source code of the project
example: https://github.com/modrinth/code
nullable: true
wiki_url:
type: string
description: An optional link to the project's wiki page or other relevant information
example: https://github.com/modrinth/code/wiki
nullable: true
discord_url:
type: string
description: An optional invite link to the project's discord.
example: https://discord.gg/modrinth
nullable: true
donation_urls:
type: array
items:
$ref: '#/components/schemas/ProjectDonationURL'
description: A list of donation links for the project
client_side:
type: string
enum: [required, optional, unsupported, unknown]
description: The client side support of the project
enum: [required, optional, unsupported]
description: Deprecated - use `environment` instead.
example: required
deprecated: true
server_side:
type: string
enum: [required, optional, unsupported, unknown]
description: The server side support of the project
enum: [required, optional, unsupported]
description: Deprecated - use `environment` instead.
example: optional
# Fields added to search results and direct project lookups that cannot be edited.
ServerRenderedProject:
allOf:
- $ref: '#/components/schemas/BaseProject'
- type: object
properties:
project_type:
type: string
enum: [mod, modpack, resourcepack, shader]
description: The project type of the project
example: mod
downloads:
type: integer
description: The total number of downloads of the project
icon_url:
type: string
example: https://cdn.modrinth.com/data/AABBCCDD/b46513nd83hb4792a9a0e1fn28fgi6090c1842639.png
description: The URL of the project's icon
nullable: true
color:
type: integer
example: 8703084
description: The RGB color of the project, automatically generated from the project icon
nullable: true
thread_id:
type: string
example: TTUUVVWW
description: The ID of the moderation thread associated with this project
monetization_status:
type: string
enum: [monetized, demonetized, force-demonetized]
required:
- project_type
- downloads
# The actual result in search.
ProjectResult:
allOf:
- $ref: '#/components/schemas/ServerRenderedProject'
- type: object
properties:
project_id:
type: string
description: The ID of the project
example: AABBCCDD
all_project_types:
type: array
items:
type: string
description: All project types across every version of the project, unlike `project_type` which only reflects a version-specific type
example: [mod, plugin, datapack]
author:
type: string
description: The username of the project's author
example: my_user
display_categories:
type: array
items:
type: string
description: A list of the categories that the project has which are not secondary
example: ['technology', 'fabric']
versions:
type: array
items:
type: string
description: A list of the minecraft versions supported by the project
example: ['1.8', '1.8.9']
follows:
type: integer
description: The total number of users following the project
date_created:
type: string
format: ISO-8601
description: The date the project was added to search
date_modified:
type: string
format: ISO-8601
description: The date the project was last modified
latest_version:
type: string
description: The latest version of minecraft that this project supports
example: 1.8.9
license:
type: string
description: The SPDX license ID of a project
example: MIT
gallery:
type: array
description: All gallery images attached to the project
example:
[
'https://cdn.modrinth.com/data/AABBCCDD/images/009b7d8d6e8bf04968a29421117c59b3efe2351a.png',
'https://cdn.modrinth.com/data/AABBCCDD/images/c21776867afb6046fdc3c21dbcf5cc50ae27a236.png',
]
items:
type: string
featured_gallery:
type: string
description: The featured gallery image of the project
nullable: true
required:
- slug
- title
- description
- client_side
- server_side
- project_id
- all_project_types
- author
- versions
- follows
- date_created
- date_modified
- license
# Fields that appear everywhere EXCEPT search.
NonSearchProject:
allOf:
- $ref: '#/components/schemas/BaseProject'
- type: object
properties:
body:
type: string
description: A long form description of the project
example: A long body describing my project in detail
status:
type: string
enum:
[
approved,
archived,
rejected,
draft,
unlisted,
processing,
withheld,
scheduled,
private,
unknown,
]
description: The status of the project
example: approved
requested_status:
type: string
enum: [approved, archived, unlisted, private, draft]
description: The requested status when submitting for review or scheduling the project for release
nullable: true
additional_categories:
type: array
items:
type: string
description: A list of categories which are searchable but non-primary
example: [technology, adventure, fabric]
issues_url:
type: string
description: An optional link to where to submit bugs or issues with the project
example: https://github.com/my_user/my_project/issues
nullable: true
source_url:
type: string
description: An optional link to the source code of the project
example: https://github.com/my_user/my_project
nullable: true
wiki_url:
type: string
description: An optional link to the project's wiki page or other relevant information
example: https://github.com/my_user/my_project/wiki
nullable: true
discord_url:
type: string
description: An optional invite link to the project's discord
example: https://discord.gg/AaBbCcDd
nullable: true
donation_urls:
type: array
items:
$ref: '#/components/schemas/ProjectDonationURL'
description: A list of donation links for the project
deprecated: true
ProjectDonationURL:
type: object
properties:
@@ -888,6 +940,7 @@ components:
project_type:
type: string
enum: [mod, modpack]
description: The project type of the project
example: modpack
initial_versions:
type: array
@@ -941,89 +994,225 @@ components:
type: integer
description: The order of the gallery image. Gallery images are sorted by this field and then alphabetically by title.
example: 0
# GET /project response. Fields ordered: required, then optional, then deprecated.
Project:
allOf:
- $ref: '#/components/schemas/NonSearchProject'
- $ref: '#/components/schemas/ServerRenderedProject'
- type: object
properties:
id:
type: string
example: AABBCCDD
description: The ID of the project, encoded as a base62 string
team:
type: string
example: MMNNOOPP
description: The ID of the team that has ownership of this project
body_url:
type: string
deprecated: true
default: null
description: The link to the long description of the project. Always null, only kept for legacy compatibility.
example: null
nullable: true
moderator_message:
$ref: '#/components/schemas/ModeratorMessage'
published:
type: string
format: ISO-8601
description: The date the project was published
updated:
type: string
format: ISO-8601
description: The date the project was last updated
approved:
type: string
format: ISO-8601
description: The date the project's status was set to an approved status
nullable: true
queued:
type: string
format: ISO-8601
description: The date the project's status was submitted to moderators for review
nullable: true
followers:
type: integer
description: The total number of users following the project
license:
$ref: '#/components/schemas/ProjectLicense'
versions:
type: array
items:
type: string
example: [IIJJKKLL, QQRRSSTT]
description: A list of the version IDs of the project (will never be empty unless `draft` status)
game_versions:
type: array
items:
type: string
example: ['1.19', '1.19.1', '1.19.2', '1.19.3']
description: A list of all of the game versions supported by the project
loaders:
type: array
items:
type: string
example: ['forge', 'fabric', 'quilt']
description: A list of all of the loaders supported by the project
gallery:
type: array
items:
$ref: '#/components/schemas/GalleryImage'
description: A list of images that have been uploaded to the project's gallery
required:
- id
- team
- published
- updated
- followers
- title
- description
- categories
- client_side
- server_side
- slug
- body
- status
type: object
properties:
id:
type: string
example: AABBCCDD
description: The ID of the project, encoded as a base62 string
team:
type: string
example: MMNNOOPP
description: The ID of the team that has ownership of this project
title:
type: string
description: The title or name of the project
description:
type: string
description: A short sentence summarizing the project, no more than a sentence or two.
body:
type: string
description: A long form description of the project
example: A long body describing my project in detail
status:
type: string
enum:
[
approved,
archived,
rejected,
draft,
unlisted,
processing,
withheld,
scheduled,
private,
unknown,
]
description: The status of the project
example: approved
project_type:
type: string
enum: [mod, modpack, resourcepack, shader]
description: The project type of the project
example: mod
categories:
type: array
items:
type: string
example: [technology, adventure, fabric]
description: A list of the featured categories that the project has.
additional_categories:
type: array
items:
type: string
description: A list of additional categories that the project also has. These are supplementary to the featured categories, and does not include them again.
example: [technology, adventure, fabric]
environment:
type: array
items:
$ref: '#/components/schemas/EnvironmentEnum'
description: 'All the environments that versions of this project support. Not in any particular order, we recommend using the environment information on a version instead. For an explanation of each environment, see the blog post here: https://modrinth.com/news/article/new-environments/#new-system'
example: [client_and_server]
game_versions:
type: array
items:
type: string
example: ['1.19', '1.19.1', '1.19.2', '1.19.3']
description: A list of all of the game versions supported by the project
loaders:
type: array
items:
type: string
example: ['neoforge', 'fabric']
description: A list of all of the loaders supported by the project. These vary based on project type.
versions:
type: array
items:
type: string
example: [IIJJKKLL, QQRRSSTT]
description: A list of the version IDs of the project
license:
$ref: '#/components/schemas/ProjectLicense'
published:
type: string
format: ISO-8601
description: The date the project was created
updated:
type: string
format: ISO-8601
description: The date the latest version of the project was created
downloads:
type: integer
description: The total number of downloads of the project
followers:
type: integer
description: The total number of users following the project
gallery:
type: array
items:
$ref: '#/components/schemas/GalleryImage'
description: A list of images that have been uploaded to the project's gallery
thread_id:
type: string
example: TTUUVVWW
description: The ID of the moderation thread associated with this project
monetization_status:
type: string
enum: [monetized, demonetized, force-demonetized]
slug:
type: string
description: "The slug of a project, used for vanity URLs. Regex: ```^[\\w!@$()`.+,\"\\-']{3,64}$```"
example: my_project
nullable: true
organization:
type: string
description: The ID of the organization that owns this project
example: AABBCCDD
nullable: true
requested_status:
type: string
enum: [approved, archived, unlisted, private, draft]
description: The requested status when submitting for review or scheduling the project for release. Approved status refers to "Public" visibility.
nullable: true
approved:
type: string
format: ISO-8601
description: The date the project was first published
nullable: true
queued:
type: string
format: ISO-8601
description: The date the project's status was submitted to moderators for review
nullable: true
icon_url:
type: string
example: https://cdn.modrinth.com/data/AABBCCDD/b46513nd83hb4792a9a0e1fn28fgi6090c1842639.png
description: The URL of the project's icon
nullable: true
raw_icon_url:
type: string
example: https://cdn.modrinth.com/data/AABBCCDD/b46513nd83hb4792a9a0e1fn28fgi6090c1842639-raw.png
description: The URL of the project's icon without CDN transforms applied
nullable: true
color:
type: integer
example: 8703084
description: The RGB color of the project, automatically generated from the project icon
nullable: true
issues_url:
type: string
description: An optional link to where to submit bugs or issues with the project
example: https://github.com/modrinth/code/issues
nullable: true
source_url:
type: string
description: An optional link to the source code of the project
example: https://github.com/modrinth/code
nullable: true
wiki_url:
type: string
description: An optional link to the project's wiki page or other relevant information
example: https://github.com/modrinth/code/wiki
nullable: true
discord_url:
type: string
description: An optional invite link to the project's discord.
example: https://discord.gg/modrinth
nullable: true
donation_urls:
type: array
items:
$ref: '#/components/schemas/ProjectDonationURL'
description: A list of donation links for the project
nullable: true
client_side:
type: string
enum: [required, optional, unsupported, unknown]
description: Deprecated - use `environment` instead.
example: required
deprecated: true
server_side:
type: string
enum: [required, optional, unsupported, unknown]
description: Deprecated - use `environment` instead.
example: optional
deprecated: true
body_url:
type: string
deprecated: true
default: null
description: Deprecated - The link to the long description of the project. Always null, only kept for legacy compatibility.
example: null
nullable: true
moderator_message:
$ref: '#/components/schemas/ModeratorMessage'
required:
- id
- team
- title
- description
- body
- status
- project_type
- categories
- additional_categories
- environment
- game_versions
- loaders
- versions
- license
- published
- updated
- downloads
- followers
- gallery
- thread_id
- monetization_status
- client_side
- server_side
ModeratorMessage:
deprecated: true
type: object
@@ -1037,7 +1226,7 @@ components:
nullable: true
nullable: true
example: null
description: A message that a moderator sent regarding the project
description: Deprecated - A message that a moderator sent regarding the project
ProjectLicense:
type: object
properties:
@@ -1970,9 +2159,10 @@ paths:
- `all_project_types` (matches against every project type across all of the project's versions, not just the primary/version-specific type)
- `categories` (loaders are lumped in with categories in search)
- `versions`
- `client_side`
- `server_side`
- `open_source`
- `environment`
- `client_side` (deprecated - use `environment` instead)
- `server_side` (deprecated - use `environment` instead)
Several others are also available for use, though these should not be used outside very specific use cases.
- `title`
-1
View File
@@ -1 +0,0 @@
CLAUDE.md
+42
View File
@@ -0,0 +1,42 @@
# apps/frontend — Modrinth Website
Nuxt 3 application serving the main Modrinth website. Uses Vue 3, Tailwind CSS v3, and file-based routing.
## Architecture
Nuxt 3 with SSR — pages are server-rendered and hydrated on the client. Uses `$fetch` for server-side data fetching and `@modrinth/api-client` (via `NuxtModrinthClient`) for client-side API calls.
## Key Directories
- **`src/pages/`** — file-based routing (`[param].vue` for dynamic segments, nested folders for nested routes)
- **`src/components/`** — website-specific components (not shared with the app)
- **`src/composables/`** — Vue composables, including `queries/` for TanStack Query options
- **`src/providers/`** — page-level DI context providers (e.g., version modal, project page)
- **`src/plugins/`** — Nuxt plugins (TanStack Query setup, theme, etc.)
- **`src/middleware/`** — route guards and auth checks
- **`src/layouts/`** — Nuxt layout components
- **`src/server/`** — server-side plugins, routes, and utilities
- **`src/store/`** — Pinia state management
- **`src/helpers/`** — utility functions
- **`src/locales/`** — i18n translation files
## Components
**Website-specific components go in `src/components/`.** These are components that only make sense in the website context — admin panels, moderation tools, dashboard widgets, brand components, etc.
**Shared components go in `packages/ui`.** If a component could be used by both the website and the desktop app, it belongs in `packages/ui/src/components/`. See `packages/ui/AGENTS.md` for UI standards, color rules, and component patterns.
Rule of thumb: if it doesn't depend on Nuxt-specific APIs or website-only features, it should be in `packages/ui`.
## Data Fetching
Use `@modrinth/api-client` via `injectModrinthClient()` for all API calls. See `packages/api-client/AGENTS.md` for the full API client documentation.
For caching and server state, use TanStack Query (`@tanstack/vue-query`). See the `tanstack-query` skill (`.agents/skills/tanstack-query/SKILL.md`) for patterns and conventions used in this codebase.
### Deprecated Composables
These composables are deprecated and should not be used in new code:
- **`useAsyncData`** - we use tanstack, not nuxt's built in async data utility.
- **`useBaseFetch`** (`src/composables/fetch.js`) — legacy Labrinth fetch wrapper. Use `client.labrinth.*` modules instead.
-42
View File
@@ -1,42 +0,0 @@
# apps/frontend — Modrinth Website
Nuxt 3 application serving the main Modrinth website. Uses Vue 3, Tailwind CSS v3, and file-based routing.
## Architecture
Nuxt 3 with SSR — pages are server-rendered and hydrated on the client. Uses `$fetch` for server-side data fetching and `@modrinth/api-client` (via `NuxtModrinthClient`) for client-side API calls.
## Key Directories
- **`src/pages/`** — file-based routing (`[param].vue` for dynamic segments, nested folders for nested routes)
- **`src/components/`** — website-specific components (not shared with the app)
- **`src/composables/`** — Vue composables, including `queries/` for TanStack Query options
- **`src/providers/`** — page-level DI context providers (e.g., version modal, project page)
- **`src/plugins/`** — Nuxt plugins (TanStack Query setup, theme, etc.)
- **`src/middleware/`** — route guards and auth checks
- **`src/layouts/`** — Nuxt layout components
- **`src/server/`** — server-side plugins, routes, and utilities
- **`src/store/`** — Pinia state management
- **`src/helpers/`** — utility functions
- **`src/locales/`** — i18n translation files
## Components
**Website-specific components go in `src/components/`.** These are components that only make sense in the website context — admin panels, moderation tools, dashboard widgets, brand components, etc.
**Shared components go in `packages/ui`.** If a component could be used by both the website and the desktop app, it belongs in `packages/ui/src/components/`. See `packages/ui/CLAUDE.md` for UI standards, color rules, and component patterns.
Rule of thumb: if it doesn't depend on Nuxt-specific APIs or website-only features, it should be in `packages/ui`.
## Data Fetching
Use `@modrinth/api-client` via `injectModrinthClient()` for all API calls. See `packages/api-client/CLAUDE.md` for the full API client documentation.
For caching and server state, use TanStack Query (`@tanstack/vue-query`). See the `tanstack-query` skill (`.claude/skills/tanstack-query/SKILL.md`) for patterns and conventions used in this codebase.
### Deprecated Composables
These composables are deprecated and should not be used in new code:
- **`useAsyncData`** - we use tanstack, not nuxt's built in async data utility.
- **`useBaseFetch`** (`src/composables/fetch.js`) — legacy Labrinth fetch wrapper. Use `client.labrinth.*` modules instead.
+3
View File
@@ -15,9 +15,12 @@ import { I18nDebugPanel, LoadingBar, NotificationPanel } from '@modrinth/ui'
import AdsConsentNotification from '~/components/ui/AdsConsentNotification.vue'
import { setupProviders } from '~/providers/setup.ts'
import { setupUserCountryProvider } from '~/providers/setup/user-country.ts'
import { useAuth } from './composables/auth'
setupUserCountryProvider()
const auth = await useAuth()
setupProviders(auth)
</script>
@@ -2,10 +2,11 @@
import {
BlueskyIcon,
DiscordIcon,
GithubIcon,
MastodonIcon,
RedditIcon,
ToggleRightIcon,
TwitterIcon,
YouTubeIcon,
} from '@modrinth/assets'
import { Button, ButtonLink } from '@modrinth/ui'
import {
@@ -94,9 +95,14 @@ const socialLinks: {
icon: TwitterIcon,
},
{
label: defineMessage({ id: 'layout.footer.social.github', defaultMessage: 'GitHub' }),
href: 'https://github.com/modrinth',
icon: GithubIcon,
label: defineMessage({ id: 'layout.footer.social.youtube', defaultMessage: 'YouTube' }),
href: 'https://www.youtube.com/@modrinth',
icon: YouTubeIcon,
},
{
label: defineMessage({ id: 'layout.footer.social.reddit', defaultMessage: 'Reddit' }),
href: 'https://www.reddit.com/r/Modrinth',
icon: RedditIcon,
},
]
+2 -36
View File
@@ -1,6 +1,6 @@
import type { ISO3166 } from '@modrinth/api-client'
import { useUserCountry as useInjectedUserCountry } from '@modrinth/ui'
import { useRequestHeaders, useState } from '#imports'
import { countries, subdivisions } from '~/generated/state.json'
export const useCountries = () => {
@@ -35,38 +35,4 @@ export const useSubdivisions = (countryCode: ComputedRef<string> | Ref<string> |
return computed(() => byCountry[unref(code)] ?? [])
}
export const useUserCountry = () => {
const country = useState<string>('userCountry', () => 'US')
const fromServer = useState<boolean>('userCountryFromServer', () => false)
if (import.meta.server) {
const headers = useRequestHeaders(['cf-ipcountry', 'accept-language'])
const cf = headers['cf-ipcountry']
if (cf) {
country.value = cf.toUpperCase()
fromServer.value = true
} else {
const al = headers['accept-language'] || ''
const tag = al.split(',')[0]
const val = tag.split('-')[1]?.toLowerCase()
if (val) {
country.value = val
fromServer.value = true
}
}
}
if (import.meta.client) {
onMounted(() => {
if (fromServer.value) return
// @ts-expect-error - ignore TS not knowing about navigator.userLanguage
const lang = navigator.language || navigator.userLanguage || ''
const region = lang.split('-')[1]
if (region) {
country.value = region.toUpperCase()
}
})
}
return country
}
export const useUserCountry = useInjectedUserCountry
@@ -1919,9 +1919,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -1952,9 +1952,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
+23 -8
View File
@@ -1206,7 +1206,7 @@
"message": "Details"
},
"create-project-version.create-modal.stage.metadata.detected-loaders": {
"message": "Erkannte Plattformen"
"message": "Erkannte Loader"
},
"create-project-version.create-modal.stage.metadata.detected-versions": {
"message": "Erkannte Versionen"
@@ -1221,7 +1221,7 @@
"message": "Dateien"
},
"create-project-version.create-modal.stage.metadata.loaders": {
"message": "Plattformen"
"message": "Loader"
},
"create-project-version.create-modal.stage.metadata.metadata-tab": {
"message": "Metadaten"
@@ -1233,13 +1233,13 @@
"message": "Modpack-Versionen können nicht bearbeitet werden"
},
"create-project-version.create-modal.stage.metadata.no-dependencies-added": {
"message": "Es wurden keine Abhängigkeiten hinzugefügt."
"message": "Keine Abhängigkeiten hinzugefügt."
},
"create-project-version.create-modal.stage.metadata.no-environment-set": {
"message": "Es wurde keine Umgebung festgelegt."
"message": "Keine Umgebung wurde festgelegt."
},
"create-project-version.create-modal.stage.metadata.no-loaders-selected": {
"message": "Es wurden keine Plattformen ausgewählt."
"message": "Keine Loader ausgewählt."
},
"create-project-version.create-modal.stage.metadata.no-mod-loader": {
"message": "Kein Modloader"
@@ -2846,9 +2846,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -3929,9 +3926,27 @@
"project.settings.tags.featured-tags": {
"message": "Hervorgehobene Tags"
},
"project.settings.tags.featured-tags-required": {
"message": "Du musst mindestens ein hervorgehobenes Tag haben."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Wähle deine relevantesten Tags aus. Diese werden vor den übrigen Tags angezeigt."
},
"project.settings.tags.features-description": {
"message": "Wähle alle Funktionen aus, die dein {type} nutzt."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types} Kategorien} other {Kategorien}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types} Funktionen} other {Funktionen}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types} Auswirkungen auf die Leistung} other {Auswirkungen auf die Leistung}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types} Auflösungen} other {Auflösungen}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Du hast {count} Auflösungs-Tags ({tags}) ausgewählt. Ressourcenpakete sollten in der Regel nur einen Auflösungs-Tag haben."
},
+24 -9
View File
@@ -1206,7 +1206,7 @@
"message": "Details"
},
"create-project-version.create-modal.stage.metadata.detected-loaders": {
"message": "Erkannte Plattformen"
"message": "Erkannte Loader"
},
"create-project-version.create-modal.stage.metadata.detected-versions": {
"message": "Erkannte Versionen"
@@ -1221,7 +1221,7 @@
"message": "Dateien"
},
"create-project-version.create-modal.stage.metadata.loaders": {
"message": "Plattformen"
"message": "Loader"
},
"create-project-version.create-modal.stage.metadata.metadata-tab": {
"message": "Metadaten"
@@ -1233,13 +1233,13 @@
"message": "Modpack-Versionen können nicht bearbeitet werden"
},
"create-project-version.create-modal.stage.metadata.no-dependencies-added": {
"message": "Es wurden keine Abhängigkeiten hinzugefügt."
"message": "Keine Abhängigkeiten hinzugefügt."
},
"create-project-version.create-modal.stage.metadata.no-environment-set": {
"message": "Es wurde keine Umgebung festgelegt."
"message": "Keine Umgebung wurde festgelegt."
},
"create-project-version.create-modal.stage.metadata.no-loaders-selected": {
"message": "Es wurden keine Plattformen ausgewählt."
"message": "Keine Loader ausgewählt."
},
"create-project-version.create-modal.stage.metadata.no-mod-loader": {
"message": "Kein Modloader"
@@ -2846,9 +2846,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -3929,9 +3926,27 @@
"project.settings.tags.featured-tags": {
"message": "Hervorgehobene Tags"
},
"project.settings.tags.featured-tags-required": {
"message": "Du musst mindestens ein hervorgehobenes Tag haben."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Wähle deine relevantesten Tags aus. Diese werden vor den übrigen Tags angezeigt."
},
"project.settings.tags.features-description": {
"message": "Wähle alle Funktionen aus, die dein {type} nutzt."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types} Kategorien} other {Kategorien}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types} Funktionen} other {Funktionen}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types} Auswirkungen auf die Leistung} other {Auswirkungen auf die Leistung}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types} Auflösungen} other {Auflösungen}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Du hast {count} Auflösungs-Tags ({tags}) ausgewählt. Ressourcenpakete sollten in der Regel nur einen Auflösungs-Tag haben."
},
@@ -5007,7 +5022,7 @@
"message": "Entwicklermodus deaktivieren"
},
"settings.display.banner.developer-mode.description": {
"message": "Der <strong>Entwicklermodus</strong> ist aktiviert. Damit kannst du die internen IDs verschiedener Elemente in Modrinth anzeigen, was für Entwickler, welche die Modrinth-API verwenden, hilfreich sein kann. Klickst du 5x auf das Modrinth-Logo unten auf der Seite, um den Entwicklermodus umzuschalten."
"message": "Der <strong>Entwicklermodus</strong> ist aktiviert. Damit kannst du die internen IDs verschiedener Elemente in Modrinth anzeigen, was für Entwickler, welche die Modrinth-API verwenden, hilfreich sein kann. Klick fünfmal auf das Modrinth-Logo unten auf der Seite, um den Entwicklermodus umzuschalten."
},
"settings.display.flags.description": {
"message": "Bestimmte Funktionen auf diesem Gerät aktivieren oder deaktivieren."
+6 -3
View File
@@ -2846,15 +2846,18 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
"layout.footer.social.reddit": {
"message": "Reddit"
},
"layout.footer.social.x": {
"message": "X"
},
"layout.footer.social.youtube": {
"message": "YouTube"
},
"layout.footer.unknown-commit": {
"message": "unknown"
},
+25 -10
View File
@@ -2846,9 +2846,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -2919,10 +2916,10 @@
"message": "Actualizar a Modrinth+"
},
"layout.publish.email-verification-required.description": {
"message": "Debes verificar tu email antes de empezar a publicar en Modrinth."
"message": "Debes verificar tu correo electrónico antes de empezar a publicar en Modrinth."
},
"layout.publish.email-verification-required.title": {
"message": "Verificación de email requerida"
"message": "Verificación de correo electrónico requerida"
},
"modal.shared-instance.open-in-app.benefit.install": {
"message": "Instala automáticamente todo lo que estén jugando"
@@ -2937,7 +2934,7 @@
"message": "Obtener la App de Modrinth"
},
"modal.shared-instance.open-in-app.managed-by": {
"message": "Gestionado por"
"message": "Administrado por"
},
"modal.shared-instance.open-in-app.opening-automatically": {
"message": "La App de Modrinth se abrirá automáticamente..."
@@ -3929,9 +3926,27 @@
"project.settings.tags.featured-tags": {
"message": "Etiquetas destacadas"
},
"project.settings.tags.featured-tags-required": {
"message": "Debes seleccionar al menos una etiqueta destacada."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Selecciona las etiquetas más relevantes. Estas se mostrarán primero que las demás etiquetas."
},
"project.settings.tags.features-description": {
"message": "Selecciona todas las características que tu {type} usa."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types} categorías} other {Categorías}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types} características} other {Características}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types} impacto al rendimiento} other {Impacto al rendimiento}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types} resoluciones} other {Resoluciones}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Has seleccionado {count} etiquetas de resolución ({tags}). Típicamente los paquetes de recursos solo deberían de tener una."
},
@@ -4575,10 +4590,10 @@
"message": "Administrar claves de acceso"
},
"settings.account.security.passkey.description": {
"message": "Administra tus claves de acceso registradas o agrega una nueva."
"message": "Administra tus claves de acceso registradas o añade una nueva."
},
"settings.account.security.passkey.modal.added": {
"message": "Agregado {ago}"
"message": "Añadido {ago}"
},
"settings.account.security.passkey.modal.last-used": {
"message": "Último uso {ago}"
@@ -4629,7 +4644,7 @@
"message": "Gestionar proveedores"
},
"settings.account.security.providers.description": {
"message": "Agrega o elimina métodos de inicio de sesión de tu cuenta, incluyendo GitHub, GitLab, Microsoft, Discord, Steam y Google."
"message": "Añade o elimina métodos de inicio de sesión de tu cuenta, incluyendo GitHub, GitLab, Microsoft, Discord, Steam y Google."
},
"settings.account.security.providers.title": {
"message": "Administrar proveedores de autenticación"
@@ -4644,7 +4659,7 @@
"message": "Configurar verificación en dos pasos"
},
"settings.account.security.two-factor.description": {
"message": "Agrega una capa adicional de seguridad a tu cuenta al iniciar sesión."
"message": "Añade una capa adicional de seguridad a tu cuenta al iniciar sesión."
},
"settings.account.security.two-factor.modal.remove.header": {
"message": "Eliminar 2FA"
+12 -3
View File
@@ -8,6 +8,18 @@
"admin.billing.error.not-found": {
"message": "Usuario no encontrado"
},
"ads-consent.accept": {
"message": "Aceptar todos"
},
"ads-consent.body": {
"message": "Los anuncios hace Modrinth posible y financian pagos a creadores. Nuestros socios pueden almacenar o acceder cookies en el sitio para personalizar anuncios y medir el rendimiento."
},
"ads-consent.manage": {
"message": "Gestionar preferencias"
},
"ads-consent.reject": {
"message": "Rechazar todos"
},
"ads-consent.title": {
"message": "Tu privacidad y como anuncios soportan Modrinth"
},
@@ -2576,9 +2588,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -863,9 +863,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -1799,9 +1799,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
+23 -8
View File
@@ -234,7 +234,7 @@
"message": "Modrinth App"
},
"analytics.download-source.website": {
"message": "Site web de Modrinth"
"message": "Site web Modrinth"
},
"analytics.downloads.suffix": {
"message": "téléchargements"
@@ -1080,10 +1080,10 @@
"message": "Ce fil est fermé et aucun message ne peut y être envoyé."
},
"conversation-thread.error.closing-report": {
"message": "Impossible de fermer le rapport"
"message": "Erreur lors de la fermeture du signalement"
},
"conversation-thread.error.reopening-report": {
"message": "Impossible de rouvrir le rapport"
"message": "Erreur lors de la réouverture du signalement"
},
"conversation-thread.error.sending-message": {
"message": "Impossible d'envoyer le message"
@@ -2676,7 +2676,7 @@
"message": "Publier"
},
"layout.action.reports": {
"message": "Rapports d'examen"
"message": "Examiner les signalements"
},
"layout.action.review-projects": {
"message": "Revue de projet"
@@ -2846,9 +2846,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -2874,7 +2871,7 @@
"message": "Ouvrir le menu"
},
"layout.nav.active-reports": {
"message": "Rapports actifs"
"message": "Signalements actifs"
},
"layout.nav.discover": {
"message": "Découvrir"
@@ -3929,9 +3926,27 @@
"project.settings.tags.featured-tags": {
"message": "Tags mis en avant"
},
"project.settings.tags.featured-tags-required": {
"message": "Vous devez au moins avoir un tag mis en avant."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Sélectionnez vos tags les plus pertinents. Ils sont affichés avant le reste de vos tags."
},
"project.settings.tags.features-description": {
"message": "Sélectionnez toutes les fonctionnalités que votre {type} utilise."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types} catégories} other {Catégories}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types} fonctionnalités} other {Fonctionnalités}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types} impact sur les performances} other {Impact sur les performances}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types} résolutions} other {Résolutions}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Vous avez sélectionné {count} tags de résolution ({tags}). Les packs de ressources ne devraient typiquement avoir qu'un seul tag de résolution."
},
+3 -3
View File
@@ -17,6 +17,9 @@
"analytics.breakdown.project": {
"message": "פּרוֹיֶקט"
},
"analytics.chart.axis.playtime-hours": {
"message": "{hours} שעות"
},
"analytics.chart.legend.monetization-details.description": {
"message": "רק צפיות והורדות שבוצעו דרך Modrinth נספרות לצורך מונטיזציה, והורדות מחייבות את המשתמשים להיות מחוברים לחשבונם."
},
@@ -1565,9 +1568,6 @@
"layout.footer.social.discord": {
"message": "דיסקורד"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "מסטודון"
},
+15 -6
View File
@@ -177,14 +177,17 @@
"message": "{count} nap"
},
"analytics.chart.tooltip.duration.hours": {
"message": "{count} óra"
"message": "{count, plural, one {#óra} other {# órák}}"
},
"analytics.chart.tooltip.duration.minutes": {
"message": "{count} perc"
"message": "{count, plural, one {# perc} other {# percek}}"
},
"analytics.chart.tooltip.hide-entry": {
"message": "{name} elrejtése a grafikonon"
},
"analytics.chart.tooltip.no-data": {
"message": "Nincs adat"
},
"analytics.chart.tooltip.pinned": {
"message": "A diagram eszköztippje kitűzve"
},
@@ -426,7 +429,7 @@
"message": "az elő. idősz. kép."
},
"analytics.stat.previous-period-comparison-short": {
"message": "az előzőhöz képest"
"message": "az elő. kép."
},
"analytics.stat.revenue": {
"message": "Bevétel"
@@ -2669,9 +2672,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -3089,6 +3089,15 @@
"organization.label.downloads": {
"message": "letöltés"
},
"organization.project-transfer.id-column": {
"message": "Azonosító"
},
"organization.project-transfer.name-column": {
"message": "Név"
},
"organization.project-transfer.project-icon-alt": {
"message": "A(z) {name} ikonja"
},
"organization.projects.none-with-create-prompt": {
"message": "Ennek a szervezetnek még nincsenek projektjei. Szeretnél <create-link>létrehozni egyet</create-link>?"
},
@@ -1811,9 +1811,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
+22 -7
View File
@@ -972,7 +972,7 @@
"message": "Smetti di seguire il progetto"
},
"collection.delete-modal.description": {
"message": "Questa raccolta sarà rimossa per sempre. Quest'azione non può essere annullata."
"message": "La raccolta sarà eliminata per sempre. Questa azione è irreversibile."
},
"collection.delete-modal.title": {
"message": "Vuoi davvero eliminare questa raccolta?"
@@ -2837,9 +2837,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -2967,7 +2964,7 @@
"message": "Ammontare"
},
"modpack-scan-modal.delete-all-groups-confirmation.description": {
"message": "Questi gruppi di attribuzione e i file all'interno saranno rimossi per sempre da questo progetto. Quest'azione non può essere annullata."
"message": "Tutti i gruppi di attribuzione del progetto e i loro contenuti saranno eliminati per sempre. Questa azione è irreversibile."
},
"modpack-scan-modal.delete-all-groups.error": {
"message": "Errore nell'eliminazione dei gruppi: {error}"
@@ -3801,7 +3798,7 @@
"message": "Elimina tutti i gruppi"
},
"project.settings.permissions.delete-all-groups-confirmation.description": {
"message": "{count, plural, one {Questo gruppo di attribuzione} other {Questi # gruppi di attribuzione}} e i file all'interno saranno rimossi per sempre da questo progetto. Quest'azione non può essere annullata."
"message": "{count, plural, one {Il gruppo di attribuzione del progetto e i suoi} other {# gruppi di attribuzione del progetto e i loro}} contenuti saranno eliminati per sempre. Questa azione è irreversibile."
},
"project.settings.permissions.delete-all-groups-confirmation.title": {
"message": "Eliminare tutti i gruppi di attribuzione?"
@@ -3917,6 +3914,24 @@
"project.settings.tags.featured-tags": {
"message": "Tag in evidenza"
},
"project.settings.tags.featured-tags-required": {
"message": "Devi avere almeno un tag in evidenza."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Seleziona i tag più pertinenti. Questi sono mostrati prima degli altri tag."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types}: categorie} other {Categorie}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types}: caratteristiche} other {Caratteristiche}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types}: impatto} other {Impatto}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types}: risoluzioni} other {Risoluzioni}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Hai selezionato {count} tag di risoluzione ({tags}). I pacchetti di risorse dovrebbero averne solo uno."
},
@@ -5190,7 +5205,7 @@
"message": "Tutte le versioni"
},
"version.confirm-delete.description": {
"message": "Questa versione verrà eliminata permanentemente. Questa azione non può essere annullata."
"message": "La versione sarà eliminata per sempre. Questa azione è irreversibile."
},
"version.confirm-delete.proceed": {
"message": "Elimina versione"
+66 -6
View File
@@ -38,6 +38,9 @@
"analytics.breakdown.country": {
"message": "国 / 地域"
},
"analytics.breakdown.dependent-on": {
"message": "次に依存"
},
"analytics.breakdown.dependent-project-download": {
"message": "依存プロジェクト"
},
@@ -131,6 +134,18 @@
"analytics.chart.empty.select-table-items": {
"message": "以下の表から項目を選択して、データを可視化してください。"
},
"analytics.chart.events.count-aria": {
"message": "{count, plural, one {# 件のイベント} other {# 件のイベント}}"
},
"analytics.chart.events.see-announcement": {
"message": "お知らせを見る"
},
"analytics.chart.legend.monetization-details.aria": {
"message": "収益に関する分析の詳細を表示"
},
"analytics.chart.legend.monetization-details.description": {
"message": "収益化に向けてModrinthを介して行われたビューとダウンロードのみが表示され、ダウンロードにはログインが必要です。"
},
"analytics.chart.table-selection.top": {
"message": "テーブル内の{itemType, select, project {{count, plural, other {プロジェクト}}} country {{count, plural, other {国}}} monetization {{count, plural, other {収益}}} downloadSource {{count, plural, other {ダウンロード元}}} downloadReason {{count, plural, other {ダウンロード理由}}} member {{count, plural, other {メンバー}}} projectVersion {{count, plural, other {プロジェクトバージョン}}} loader {{count, plural, other {ローダー}}} gameVersion {{count, plural, other {ゲームバージョン}}} other {{count, plural, other {項目}}}}上位 {count} 件を表示"
},
@@ -140,6 +155,12 @@
"analytics.chart.tooltip.dependent-project-version": {
"message": "{dependentProject} は {dependencyProject} の {version} に依存しています"
},
"analytics.chart.tooltip.no-data": {
"message": "データなし"
},
"analytics.chart.tooltip.pinned-aria": {
"message": "ピン留め"
},
"analytics.chart.tooltip.total": {
"message": "合計"
},
@@ -269,9 +290,27 @@
"analytics.project-event.project-approved": {
"message": "承認済み"
},
"analytics.project-event.project-private": {
"message": "プロジェクトを非公開に設定"
},
"analytics.project-event.project-status-changed": {
"message": "プロジェクトの状態が変更"
},
"analytics.project-event.project-unlisted": {
"message": "プロジェクトはリストにありません"
},
"analytics.project-event.version-released": {
"message": "{version} がリリースされました"
},
"analytics.project-event.version-uploaded": {
"message": "バージョンがアップロードされました"
},
"analytics.project-status.approved": {
"message": "承認済み"
},
"analytics.project-status.archived": {
"message": "アーカイブ済み"
},
"analytics.project-status.draft": {
"message": "ドラフト"
},
@@ -281,6 +320,9 @@
"analytics.project-status.private": {
"message": "非公開"
},
"analytics.project-status.rejected": {
"message": "却下"
},
"analytics.project-status.unlisted": {
"message": "限定公開"
},
@@ -741,7 +783,7 @@
"message": "アカウント設定"
},
"auth.verify-email.action.discover-mods": {
"message": "早速Modを探す"
"message": "Modを探す"
},
"auth.verify-email.already-verified.description": {
"message": "メールアドレスはすでに承認されています!"
@@ -2022,7 +2064,7 @@
"message": "プランを選択"
},
"landing.button.discover-mods": {
"message": "Modを探索する"
"message": "Modを探"
},
"landing.button.go-to-dashboard": {
"message": "ダッシュボードへ移動"
@@ -2043,7 +2085,7 @@
"message": "検索結果やホームページ、Discordサーバー、そして今後追加される様々なルートを通じて、数千人ものユーザーにあなたのプロジェクトを見つけてもらいましょう!"
},
"landing.creator.feature.discovery.title": {
"message": "探索"
"message": "発見"
},
"landing.creator.feature.diverse-ecosystem.description": {
"message": "Minotaurを使ってビルドツールと連携すれば、新バージョンのリリースと同時に自動アップロードが可能です。"
@@ -2336,9 +2378,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "Github"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -2933,6 +2972,27 @@
"project.moderation.admonition.draft.header": {
"message": "ドラフト"
},
"project.moderation.admonition.under-review.body.1": {
"message": "あなたのプロジェクトはモデレーターによる審査待ちとなっております。"
},
"project.moderation.admonition.under-review.body.2": {
"message": "あなたのプロジェクトはスキャンされ、その後、人間のモデレーターによって審査され、Modrinth の <rules-link>コンテンツルール</rules-link> および <terms-link>利用規約</terms-link> に準拠しているかどうかが確認されます。"
},
"project.moderation.admonition.under-review.body.3": {
"message": "審査を待っている間もプロジェクトを編集することができます。編集をしても審査の順番が変わるわけではございません。"
},
"project.moderation.admonition.under-review.body.4": {
"message": "提出されたプロジェクトは24~48時間以内に審査することを目指していますが、プロジェクトによっては遅れが発生してしまう場合があります。プロジェクトに問題があることを意味するというわけではありません。"
},
"project.moderation.admonition.under-review.body.4.alt-week": {
"message": "プロジェクトの審査は1週間以内に行うことを目標としていますが遅れが発生してしまう場合がございます。プロジェクトに問題があることを意味するというわけではありません。"
},
"project.moderation.admonition.under-review.body.5": {
"message": "<emphasis>モデレーターがModrinthの安全を守るために尽くしております。ご協力お願いします。皆様のプロジェクト審査をサポートできることを楽しみにしております💚</emphasis>"
},
"project.moderation.admonition.under-review.header": {
"message": "プロジェクトは審査中です"
},
"project.moderation.title": {
"message": "管理"
},
@@ -2846,9 +2846,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -2336,9 +2336,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
+18 -3
View File
@@ -2843,9 +2843,6 @@
"layout.footer.social.discord": {
"message": "Discord "
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -3926,9 +3923,27 @@
"project.settings.tags.featured-tags": {
"message": "Uitgelichte tags"
},
"project.settings.tags.featured-tags-required": {
"message": "Je moet minstens één uitgelichte tag hebben."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Kies je meest relevante tags. Deze worden vóór de overige tags weergegeven."
},
"project.settings.tags.features-description": {
"message": "Selecteer alle functies waarvan je {type} gebruikmaakt."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types} categorieën} other {Categorieën}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types} functies} other {Functies}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types} invloed op de prestaties} other {Invloed op de prestaties}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types} resoluties} other {Resoluties}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Je hebt {count} resolutietags ({tags}) geselecteerd. Bronpakketten zouden normaal gesproken slechts één resolutietag moeten hebben."
},
@@ -2255,9 +2255,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "Github"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
+3 -3
View File
@@ -2840,9 +2840,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -3854,6 +3851,9 @@
"project.settings.server.languages-label": {
"message": "Języki"
},
"project.settings.server.latency-label": {
"message": "Opóźnienie: {latency}ms"
},
"project.settings.server.optional-label": {
"message": "opcjonalne"
},
+18 -3
View File
@@ -2846,9 +2846,6 @@
"layout.footer.social.discord": {
"message": "Discord"
},
"layout.footer.social.github": {
"message": "GitHub"
},
"layout.footer.social.mastodon": {
"message": "Mastodon"
},
@@ -3929,9 +3926,27 @@
"project.settings.tags.featured-tags": {
"message": "Etiquetas em destaque"
},
"project.settings.tags.featured-tags-required": {
"message": "Você deve ter pelo menos uma marcação em destaque."
},
"project.settings.tags.featured-tags-select-description": {
"message": "Selecione suas marcações mais relevantes. Estas serão exibidas antes das outras marcações."
},
"project.settings.tags.features-description": {
"message": "Selecione todas as funcionalidades que o seu {type} utiliza."
},
"project.settings.tags.group-title.categories": {
"message": "{showType, select, yes {{types} categorias} other {Categorias}}"
},
"project.settings.tags.group-title.features": {
"message": "{showType, select, yes {{types} recursos} other {Recursos}}"
},
"project.settings.tags.group-title.performance-impact": {
"message": "{showType, select, yes {{types} impacto no desempenho} other {Impacto no desempenho}}"
},
"project.settings.tags.group-title.resolutions": {
"message": "{showType, select, yes {{types} resoluções} other {Resoluções}}"
},
"project.settings.tags.multiple-resolution-tags-warning": {
"message": "Você selecionou {count} etiquetas de resolução ({tags}). Os pacotes de recursos devem normalmente ter apenas uma etiqueta de resolução."
},

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