diff --git a/.github/stale.yml b/.github/stale.yml index eeed081e3..a0c96e871 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,18 +1,44 @@ -# Number of days of inactivity before an issue becomes stale +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 7 -# Issues with these labels will never be considered stale + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - pinned - security - dependencies -# Label to use when marking an issue as stale + - never-stale + - priority:high + - priority:medium + +# Label to use when marking as stale staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable + +# Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +pulls: + markComment: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. diff --git a/Dockerfile b/Dockerfile index 5140b30e4..c38730f58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,10 @@ ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64} RUN \ case "${TARGETPLATFORM}" in \ - 'linux/arm64') apk add --no-cache python make g++ ;; \ - 'linux/arm/v7') apk add --no-cache python make g++ ;; \ + 'linux/arm64' | 'linux/arm/v7') \ + apk add --no-cache python3 make g++ && \ + ln -s /usr/bin/python3 /usr/bin/python \ + ;; \ esac COPY package.json yarn.lock ./ diff --git a/docs/extending-overseerr/third-party.md b/docs/extending-overseerr/third-party.md index cf2946d91..7ff2bcabf 100644 --- a/docs/extending-overseerr/third-party.md +++ b/docs/extending-overseerr/third-party.md @@ -1,7 +1,7 @@ # Third-Party Integrations {% hint style="warning" %} -We do not officially support these third-party integrations. If you run into any issues, please seek help on the appropriate support channels for the integration itself! +**We do not officially support these third-party integrations.** If you run into any issues, please seek help on the appropriate support channels for the integration itself! {% endhint %} - [Organizr](https://organizr.app/), a HTPC/homelab services organizer @@ -9,6 +9,7 @@ We do not officially support these third-party integrations. If you run into any - [LunaSea](https://docs.lunasea.app/modules/overseerr), a self-hosted controller for mobile and macOS - [Requestrr](https://github.com/darkalfx/requestrr/wiki/Configuring-Overseerr), a Discord chatbot - [Doplarr](https://github.com/kiranshila/Doplarr), a Discord request bot +- [Overseerr Assistant](https://github.com/RemiRigal/Overseerr-Assistant), a browser extension for requesting directly from TMDb and IMDb - [ha-overseerr](https://github.com/vaparr/ha-overseerr), a custom Home Assistant component - [OverCLIrr](https://github.com/WillFantom/OverCLIrr), a command-line tool - [Overseerr Exporter](https://github.com/WillFantom/overseerr-exporter), a Prometheus exporter diff --git a/docs/support/faq.md b/docs/support/faq.md index 19d71e931..d3ea3cdce 100644 --- a/docs/support/faq.md +++ b/docs/support/faq.md @@ -20,6 +20,12 @@ A more advanced, user-friendly, and secure (if using SSL) method is to set up a The most secure method (but also the most inconvenient method) is to set up a VPN tunnel to your home server. You would then be able to access Overseerr as if you were on your local network, via `http://LOCAL-IP-ADDRESS:5055`. +### Are there mobile apps for Overseerr? + +Since Overseerr has an almost native app experience when installed as a Progressive Web App (PWA), there are no plans to develop mobile apps for Overseerr. + +Out of the box, Overseerr already fulfills most of the [PWA install criteria](https://web.dev/install-criteria/). You simply need to make sure that your Overseerr instance is being served over HTTPS (e.g., via a [reverse proxy](../extending-overseerr/reverse-proxy.md)). + ### Overseerr is amazing! But it is not translated in my language yet! Can I help with translations? You sure can! We are using [Weblate](https://hosted.weblate.org/engage/overseerr/) for translations. If your language is not listed, please [open a feature request on GitHub](https://github.com/sct/overseerr/issues/new/choose). diff --git a/docs/using-overseerr/notifications/pushbullet.md b/docs/using-overseerr/notifications/pushbullet.md index 45edcc3a0..6c6ba853e 100644 --- a/docs/using-overseerr/notifications/pushbullet.md +++ b/docs/using-overseerr/notifications/pushbullet.md @@ -1,5 +1,11 @@ # Pushbullet +{% hint style="info" %} +Users can optionally configure personal notifications in their user settings. + +User notifications are separate from system notifications, and the available notification types are dependent on user permissions. +{% endhint %} + ## Configuration ### Access Token diff --git a/docs/using-overseerr/notifications/pushover.md b/docs/using-overseerr/notifications/pushover.md index 55893dbad..cc09bfb69 100644 --- a/docs/using-overseerr/notifications/pushover.md +++ b/docs/using-overseerr/notifications/pushover.md @@ -1,5 +1,11 @@ # Pushover +{% hint style="info" %} +Users can optionally configure personal notifications in their user settings. + +User notifications are separate from system notifications, and the available notification types are dependent on user permissions. +{% endhint %} + ## Configuration ### Application/API Token diff --git a/docs/using-overseerr/notifications/telegram.md b/docs/using-overseerr/notifications/telegram.md index d0e6f6fcb..9bdb96dbc 100644 --- a/docs/using-overseerr/notifications/telegram.md +++ b/docs/using-overseerr/notifications/telegram.md @@ -1,7 +1,9 @@ # Telegram {% hint style="info" %} -Users can optionally configure their own notifications in their user settings. +Users can optionally configure personal notifications in their user settings. + +User notifications are separate from system notifications, and the available notification types are dependent on user permissions. {% endhint %} ## Configuration diff --git a/docs/using-overseerr/notifications/webhooks.md b/docs/using-overseerr/notifications/webhooks.md index c16374808..686f82bed 100644 --- a/docs/using-overseerr/notifications/webhooks.md +++ b/docs/using-overseerr/notifications/webhooks.md @@ -24,23 +24,28 @@ Customize the JSON payload to suit your needs. Overseerr provides several [templ ### General -- `{{notification_type}}` The type of notification. (Ex. `MEDIA_PENDING` or `MEDIA_APPROVED`) -- `{{subject}}` The notification subject message. (For request notifications, this is the media title) -- `{{message}}` Notification message body. (For request notifications, this is the media's overview/synopsis) -- `{{image}}` Associated image with the request. (For request notifications, this is the media's poster) +| Variable | Value | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `{{notification_type}}` | The type of notification (e.g. `MEDIA_PENDING` or `ISSUE_COMMENT`) | +| `{{event}}` | A friendly description of the notification event | +| `{{subject}}` | The notification subject (typically the media title) | +| `{{message}}` | The notification message body (the media overview/synopsis for request notifications; the issue description for issue notificatons) | +| `{{image}}` | The notification image (typically the media poster) | -### User +### Notify User These variables are for the target recipient of the notification. -- `{{notifyuser_username}}` Target user's username. -- `{{notifyuser_email}}` Target user's email address. -- `{{notifyuser_avatar}}` Target user's avatar URL. -- `{{notifyuser_settings_discordId}}` Target user's Discord ID (if one is set). -- `{{notifyuser_settings_telegramChatId}}` Target user's Telegram Chat ID (if one is set). +| Variable | Value | +| ---------------------------------------- | ------------------------------------------------------------- | +| `{{notifyuser_username}}` | The target notification recipient's username | +| `{{notifyuser_email}}` | The target notification recipient's email address | +| `{{notifyuser_avatar}}` | The target notification recipient's avatar URL | +| `{{notifyuser_settings_discordId}}` | The target notification recipient's Discord ID (if set) | +| `{{notifyuser_settings_telegramChatId}}` | The target notification recipient's Telegram Chat ID (if set) | {% hint style="info" %} -The `notifyuser` variables are not set for the following notification types, as they are intended for application administrators rather than end users: +The `notifyuser` variables are not defined for the following request notification types, as they are intended for application administrators rather than end users: - Media Requested - Media Automatically Approved @@ -59,28 +64,69 @@ If you would like to use the requesting user's information in your webhook, plea The following variables must be used as a key in the JSON payload (e.g., `"{{extra}}": []`). -- `{{request}}` This object will be `null` if there is no relevant request object for the notification. -- `{{media}}` This object will be `null` if there is no relevant media object for the notification. -- `{{extra}}` This object will contain the "extra" array of additional data for certain notifications. +| Variable | Value | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `{{media}}` | The relevant media object | +| `{{request}}` | The relevant request object | +| `{{issue}}` | The relevant issue object | +| `{{comment}}` | The relevant issue comment object | +| `{{extra}}` | The "extra" array of additional data for certain notifications (e.g., season/episode numbers for series-related notifications) | #### Media -These `{{media}}` special variables are only included in media-related notifications, such as requests. +The `{{media}}` will be `null` if there is no relevant media object for the notification. -- `{{media_type}}` Media type (`movie` or `tv`). -- `{{media_tmdbid}}` Media's TMDb ID. -- `{{media_imdbid}}` Media's IMDb ID. -- `{{media_tvdbid}}` Media's TVDB ID. -- `{{media_status}}` Media's availability status (`UNKNOWN`, `PENDING`, `PROCESSING`, `PARTIALLY_AVAILABLE`, or `AVAILABLE`). -- `{{media_status4k}}` Media's 4K availability status (`UNKNOWN`, `PENDING`, `PROCESSING`, `PARTIALLY_AVAILABLE`, or `AVAILABLE`) +These following special variables are only included in media-related notifications, such as requests. + +| Variable | Value | +| -------------------- | -------------------------------------------------------------------------------------------------------------- | +| `{{media_type}}` | The media type (`movie` or `tv`) | +| `{{media_tmdbid}}` | The media's TMDb ID | +| `{{media_tvdbid}}` | The media's TheTVDB ID | +| `{{media_status}}` | The media's availability status (`UNKNOWN`, `PENDING`, `PROCESSING`, `PARTIALLY_AVAILABLE`, or `AVAILABLE`) | +| `{{media_status4k}}` | The media's 4K availability status (`UNKNOWN`, `PENDING`, `PROCESSING`, `PARTIALLY_AVAILABLE`, or `AVAILABLE`) | #### Request -The `{{request}}` special variables are only included in request-related notifications. +The `{{request}}` will be `null` if there is no relevant media object for the notification. -- `{{request_id}}` Request ID. -- `{{requestedBy_username}}` Requesting user's username. -- `{{requestedBy_email}}` Requesting user's email address. -- `{{requestedBy_avatar}}` Requesting user's avatar URL. -- `{{requestedBy_settings_discordId}}` Requesting user's Discord ID (if set). -- `{{requestedBy_settings_telegramChatId}}` Requesting user's Telegram Chat ID (if set). +The following special variables are only included in request-related notifications. + +| Variable | Value | +| ----------------------------------------- | ----------------------------------------------- | +| `{{request_id}}` | The request ID | +| `{{requestedBy_username}}` | The requesting user's username | +| `{{requestedBy_email}}` | The requesting user's email address | +| `{{requestedBy_avatar}}` | The requesting user's avatar URL | +| `{{requestedBy_settings_discordId}}` | The requesting user's Discord ID (if set) | +| `{{requestedBy_settings_telegramChatId}}` | The requesting user's Telegram Chat ID (if set) | + +#### Issue + +The `{{issue}}` will be `null` if there is no relevant media object for the notification. + +The following special variables are only included in issue-related notifications. + +| Variable | Value | +| ---------------------------------------- | ----------------------------------------------- | +| `{{issue_id}}` | The issue ID | +| `{{reportedBy_username}}` | The requesting user's username | +| `{{reportedBy_email}}` | The requesting user's email address | +| `{{reportedBy_avatar}}` | The requesting user's avatar URL | +| `{{reportedBy_settings_discordId}}` | The requesting user's Discord ID (if set) | +| `{{reportedBy_settings_telegramChatId}}` | The requesting user's Telegram Chat ID (if set) | + +#### Comment + +The `{{comment}}` will be `null` if there is no relevant media object for the notification. + +The following special variables are only included in issue comment-related notifications. + +| Variable | Value | +| ----------------------------------------- | ----------------------------------------------- | +| `{{comment_message}}` | The comment message | +| `{{commentedBy_username}}` | The commenting user's username | +| `{{commentedBy_email}}` | The commenting user's email address | +| `{{commentedBy_avatar}}` | The commenting user's avatar URL | +| `{{commentedBy_settings_discordId}}` | The commenting user's Discord ID (if set) | +| `{{commentedBy_settings_telegramChatId}}` | The commenting user's Telegram Chat ID (if set) | diff --git a/overseerr-api.yml b/overseerr-api.yml index bf324b029..f8be08958 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -909,6 +909,15 @@ components: type: array items: $ref: '#/components/schemas/ProductionCompany' + productionCountries: + type: array + items: + type: object + properties: + iso_3166_1: + type: string + name: + type: string spokenLanguages: type: array items: @@ -1630,6 +1639,15 @@ components: discordId: type: string nullable: true + pushbulletAccessToken: + type: string + nullable: true + pushoverApplicationToken: + type: string + nullable: true + pushoverUserKey: + type: string + nullable: true telegramEnabled: type: boolean telegramBotUsername: @@ -1687,6 +1705,36 @@ components: type: number name: type: string + Issue: + type: object + properties: + id: + type: number + example: 1 + issueType: + type: number + example: 1 + media: + $ref: '#/components/schemas/MediaInfo' + createdBy: + $ref: '#/components/schemas/User' + modifiedBy: + $ref: '#/components/schemas/User' + comments: + type: array + items: + $ref: '#/components/schemas/IssueComment' + IssueComment: + type: object + properties: + id: + type: number + example: 1 + user: + $ref: '#/components/schemas/User' + message: + type: string + example: A comment securitySchemes: cookieAuth: type: apiKey @@ -5183,7 +5231,251 @@ paths: type: array items: type: string + /issue: + get: + summary: Get all issues + description: | + Returns a list of issues in JSON format. + tags: + - issue + parameters: + - in: query + name: take + schema: + type: number + nullable: true + example: 20 + - in: query + name: skip + schema: + type: number + nullable: true + example: 0 + - in: query + name: sort + schema: + type: string + enum: [added, modified] + default: added + - in: query + name: filter + schema: + type: string + enum: [all, open, resolved] + default: open + - in: query + name: requestedBy + schema: + type: number + nullable: true + example: 1 + responses: + '200': + description: Issues returned + content: + application/json: + schema: + type: object + properties: + pageInfo: + $ref: '#/components/schemas/PageInfo' + results: + type: array + items: + $ref: '#/components/schemas/Issue' + post: + summary: Create new issue + description: | + Creates a new issue + tags: + - issue + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + issueType: + type: number + message: + type: string + mediaId: + type: number + responses: + '201': + description: Succesfully created the issue + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + /issue/{issueId}: + get: + summary: Get issue + description: | + Returns a single issue in JSON format. + tags: + - issue + parameters: + - in: path + name: issueId + required: true + schema: + type: number + example: 1 + responses: + '200': + description: Issues returned + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + delete: + summary: Delete issue + description: Removes an issue. If the user has the `MANAGE_ISSUES` permission, any issue can be removed. Otherwise, only a users own issues can be removed. + tags: + - issue + parameters: + - in: path + name: issueId + description: Issue ID + required: true + example: '1' + schema: + type: string + responses: + '204': + description: Succesfully removed issue + /issue/{issueId}/comment: + post: + summary: Create a comment + description: | + Creates a comment and returns associated issue in JSON format. + tags: + - issue + parameters: + - in: path + name: issueId + required: true + schema: + type: number + example: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + required: + - message + responses: + '200': + description: Issue returned with new comment + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + /issueComment/{commentId}: + get: + summary: Get issue comment + description: | + Returns a single issue comment in JSON format. + tags: + - issue + parameters: + - in: path + name: commentId + required: true + schema: + type: string + example: 1 + responses: + '200': + description: Comment returned + content: + application/json: + schema: + $ref: '#/components/schemas/IssueComment' + put: + summary: Update issue comment + description: | + Updates and returns a single issue comment in JSON format. + tags: + - issue + parameters: + - in: path + name: commentId + required: true + schema: + type: string + example: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + responses: + '200': + description: Comment updated + content: + application/json: + schema: + $ref: '#/components/schemas/IssueComment' + delete: + summary: Delete issue comment + description: | + Deletes an issue comment. Only users with `MANAGE_ISSUES` or the user who created the comment can perform this action. + tags: + - issue + parameters: + - in: path + name: commentId + description: Issue Comment ID + required: true + example: '1' + schema: + type: string + responses: + '204': + description: Succesfully removed issue comment + /issue/{issueId}/{status}: + post: + summary: Update an issue's status + description: | + Updates an issue's status to approved or declined. Also returns the issue in a JSON object. + Requires the `MANAGE_ISSUES` permission or `ADMIN`. + tags: + - issue + parameters: + - in: path + name: issueId + description: Issue ID + required: true + schema: + type: string + example: '1' + - in: path + name: status + description: New status + required: true + schema: + type: string + enum: [open, resolved] + responses: + '200': + description: Issue status changed + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' security: - cookieAuth: [] - apiKey: [] diff --git a/package.json b/package.json index 3c98738b0..31cd4dcb5 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "migration:run": "ts-node --project server/tsconfig.json ./node_modules/typeorm/cli.js migration:run", "format": "prettier --write ." }, + "repository": { + "type": "git", + "url": "https://github.com/sct/overseerr.git" + }, "license": "MIT", "dependencies": { "@headlessui/react": "^1.4.1", diff --git a/public/sw.js b/public/sw.js index a3c816e8f..e04d229e5 100644 --- a/public/sw.js +++ b/public/sw.js @@ -90,8 +90,8 @@ self.addEventListener('push', (event) => { if (payload.actionUrl){ options.actions.push( { - action: 'viewmedia', - title: 'View Media', + action: 'view', + title: payload.actionUrlTitle ?? 'View', } ); } @@ -119,21 +119,17 @@ self.addEventListener('notificationclick', (event) => { event.notification.close(); - if (event.action === 'viewmedia') { - clients.openWindow(notificationData.actionUrl); - } else if (event.action === 'approve') { + if (event.action === 'approve') { fetch(`/api/v1/request/${notificationData.requestId}/approve`, { method: 'POST', }); - - clients.openWindow(notificationData.actionUrl); } else if (event.action === 'decline') { fetch(`/api/v1/request/${notificationData.requestId}/decline`, { method: 'POST', }); - - clients.openWindow(notificationData.actionUrl); - } else if (notificationData.actionUrl) { + } + + if (notificationData.actionUrl) { clients.openWindow(notificationData.actionUrl); } }, false); diff --git a/server/api/plexapi.ts b/server/api/plexapi.ts index cee4a9cd1..73278387a 100644 --- a/server/api/plexapi.ts +++ b/server/api/plexapi.ts @@ -1,5 +1,6 @@ import NodePlexAPI from 'plex-api'; import { getSettings, Library, PlexSettings } from '../lib/settings'; +import logger from '../logger'; export interface PlexLibraryItem { ratingKey: string; @@ -145,28 +146,40 @@ class PlexAPI { public async syncLibraries(): Promise { const settings = getSettings(); - const libraries = await this.getLibraries(); + try { + const libraries = await this.getLibraries(); - const newLibraries: Library[] = libraries - // Remove libraries that are not movie or show - .filter((library) => library.type === 'movie' || library.type === 'show') - // Remove libraries that do not have a metadata agent set (usually personal video libraries) - .filter((library) => library.agent !== 'com.plexapp.agents.none') - .map((library) => { - const existing = settings.plex.libraries.find( - (l) => l.id === library.key && l.name === library.title - ); + const newLibraries: Library[] = libraries + // Remove libraries that are not movie or show + .filter( + (library) => library.type === 'movie' || library.type === 'show' + ) + // Remove libraries that do not have a metadata agent set (usually personal video libraries) + .filter((library) => library.agent !== 'com.plexapp.agents.none') + .map((library) => { + const existing = settings.plex.libraries.find( + (l) => l.id === library.key && l.name === library.title + ); - return { - id: library.key, - name: library.title, - enabled: existing?.enabled ?? false, - type: library.type, - lastScan: existing?.lastScan, - }; + return { + id: library.key, + name: library.title, + enabled: existing?.enabled ?? false, + type: library.type, + lastScan: existing?.lastScan, + }; + }); + + settings.plex.libraries = newLibraries; + } catch (e) { + logger.error('Failed to fetch Plex libraries', { + label: 'Plex API', + message: e.message, }); - settings.plex.libraries = newLibraries; + settings.plex.libraries = []; + } + settings.save(); } diff --git a/server/api/themoviedb/interfaces.ts b/server/api/themoviedb/interfaces.ts index bd3c2d8be..7892fe467 100644 --- a/server/api/themoviedb/interfaces.ts +++ b/server/api/themoviedb/interfaces.ts @@ -251,6 +251,10 @@ export interface TmdbTvDetails { name: string; origin_country: string; }[]; + production_countries: { + iso_3166_1: string; + name: string; + }[]; spoken_languages: { english_name: string; iso_639_1: string; diff --git a/server/constants/issue.ts b/server/constants/issue.ts new file mode 100644 index 000000000..2c9dcb697 --- /dev/null +++ b/server/constants/issue.ts @@ -0,0 +1,18 @@ +export enum IssueType { + VIDEO = 1, + AUDIO = 2, + SUBTITLES = 3, + OTHER = 4, +} + +export enum IssueStatus { + OPEN = 1, + RESOLVED = 2, +} + +export const IssueTypeName = { + [IssueType.AUDIO]: 'Audio', + [IssueType.VIDEO]: 'Video', + [IssueType.SUBTITLES]: 'Subtitle', + [IssueType.OTHER]: 'Other', +}; diff --git a/server/entity/Issue.ts b/server/entity/Issue.ts new file mode 100644 index 000000000..d8e05c565 --- /dev/null +++ b/server/entity/Issue.ts @@ -0,0 +1,68 @@ +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { IssueStatus, IssueType } from '../constants/issue'; +import IssueComment from './IssueComment'; +import Media from './Media'; +import { User } from './User'; + +@Entity() +class Issue { + @PrimaryGeneratedColumn() + public id: number; + + @Column({ type: 'int' }) + public issueType: IssueType; + + @Column({ type: 'int', default: IssueStatus.OPEN }) + public status: IssueStatus; + + @Column({ type: 'int', default: 0 }) + public problemSeason: number; + + @Column({ type: 'int', default: 0 }) + public problemEpisode: number; + + @ManyToOne(() => Media, (media) => media.issues, { + eager: true, + onDelete: 'CASCADE', + }) + public media: Media; + + @ManyToOne(() => User, (user) => user.createdIssues, { + eager: true, + onDelete: 'CASCADE', + }) + public createdBy: User; + + @ManyToOne(() => User, { + eager: true, + onDelete: 'CASCADE', + nullable: true, + }) + public modifiedBy?: User; + + @OneToMany(() => IssueComment, (comment) => comment.issue, { + cascade: true, + eager: true, + }) + public comments: IssueComment[]; + + @CreateDateColumn() + public createdAt: Date; + + @UpdateDateColumn() + public updatedAt: Date; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + +export default Issue; diff --git a/server/entity/IssueComment.ts b/server/entity/IssueComment.ts new file mode 100644 index 000000000..e45216392 --- /dev/null +++ b/server/entity/IssueComment.ts @@ -0,0 +1,42 @@ +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import Issue from './Issue'; +import { User } from './User'; + +@Entity() +class IssueComment { + @PrimaryGeneratedColumn() + public id: number; + + @ManyToOne(() => User, { + eager: true, + onDelete: 'CASCADE', + }) + public user: User; + + @ManyToOne(() => Issue, (issue) => issue.comments, { + onDelete: 'CASCADE', + }) + public issue: Issue; + + @Column({ type: 'text' }) + public message: string; + + @CreateDateColumn() + public createdAt: Date; + + @UpdateDateColumn() + public updatedAt: Date; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + +export default IssueComment; diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 9666ac289..9cb8cd793 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -16,6 +16,7 @@ import { MediaStatus, MediaType } from '../constants/media'; import downloadTracker, { DownloadingItem } from '../lib/downloadtracker'; import { getSettings } from '../lib/settings'; import logger from '../logger'; +import Issue from './Issue'; import { MediaRequest } from './MediaRequest'; import Season from './Season'; @@ -54,7 +55,7 @@ class Media { try { const media = await mediaRepository.findOne({ where: { tmdbId: id, mediaType }, - relations: ['requests'], + relations: ['requests', 'issues'], }); return media; @@ -97,6 +98,9 @@ class Media { }) public seasons: Season[]; + @OneToMany(() => Issue, (issue) => issue.media, { cascade: true }) + public issues: Issue[]; + @CreateDateColumn() public createdAt: Date; diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index a935b13f1..0e97f3d69 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -142,6 +142,7 @@ export class MediaRequest { if (this.type === MediaType.MOVIE) { const movie = await tmdb.getMovie({ movieId: media.tmdbId }); notificationManager.sendNotification(Notification.MEDIA_PENDING, { + event: `New ${this.is4k ? '4K ' : ''}Movie Request`, subject: `${movie.title}${ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' }`, @@ -153,12 +154,14 @@ export class MediaRequest { image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, media, request: this, + notifyAdmin: true, }); } if (this.type === MediaType.TV) { const tv = await tmdb.getTvShow({ tvId: media.tmdbId }); notificationManager.sendNotification(Notification.MEDIA_PENDING, { + event: `New ${this.is4k ? '4K ' : ''}Series Request`, subject: `${tv.name}${ tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : '' }`, @@ -171,13 +174,14 @@ export class MediaRequest { media, extra: [ { - name: 'Seasons', + name: 'Requested Seasons', value: this.seasons .map((season) => season.seasonNumber) .join(', '), }, ], request: this, + notifyAdmin: true, }); } } @@ -222,6 +226,13 @@ export class MediaRequest { : Notification.MEDIA_APPROVED : Notification.MEDIA_DECLINED, { + event: `${this.is4k ? '4K ' : ''}Movie Request ${ + this.status === MediaRequestStatus.APPROVED + ? autoApproved + ? 'Automatically Approved' + : 'Approved' + : 'Declined' + }`, subject: `${movie.title}${ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' }`, @@ -231,6 +242,7 @@ export class MediaRequest { omission: '…', }), image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, + notifyAdmin: autoApproved, notifyUser: autoApproved ? undefined : this.requestedBy, media, request: this, @@ -245,6 +257,13 @@ export class MediaRequest { : Notification.MEDIA_APPROVED : Notification.MEDIA_DECLINED, { + event: `${this.is4k ? '4K ' : ''}Series Request ${ + this.status === MediaRequestStatus.APPROVED + ? autoApproved + ? 'Automatically Approved' + : 'Approved' + : 'Declined' + }`, subject: `${tv.name}${ tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : '' }`, @@ -254,11 +273,12 @@ export class MediaRequest { omission: '…', }), image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`, + notifyAdmin: autoApproved, notifyUser: autoApproved ? undefined : this.requestedBy, media, extra: [ { - name: 'Seasons', + name: 'Requested Seasons', value: this.seasons .map((season) => season.seasonNumber) .join(', '), @@ -508,6 +528,7 @@ export class MediaRequest { ); notificationManager.sendNotification(Notification.MEDIA_FAILED, { + event: `${this.is4k ? '4K ' : ''}Movie Request Failed`, subject: `${movie.title}${ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' }`, @@ -519,6 +540,7 @@ export class MediaRequest { media, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, request: this, + notifyAdmin: true, }); }); logger.info('Sent request to Radarr', { label: 'Media Request' }); @@ -722,6 +744,7 @@ export class MediaRequest { ); notificationManager.sendNotification(Notification.MEDIA_FAILED, { + event: `${this.is4k ? '4K ' : ''}Series Request Failed`, subject: `${series.name}${ series.first_air_date ? ` (${series.first_air_date.slice(0, 4)})` @@ -736,13 +759,14 @@ export class MediaRequest { media, extra: [ { - name: 'Seasons', + name: 'Requested Seasons', value: this.seasons .map((season) => season.seasonNumber) .join(', '), }, ], request: this, + notifyAdmin: true, }); }); logger.info('Sent request to Sonarr', { label: 'Media Request' }); diff --git a/server/entity/User.ts b/server/entity/User.ts index 77f0e8b11..d54e31ae5 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -27,6 +27,7 @@ import { } from '../lib/permissions'; import { getSettings } from '../lib/settings'; import logger from '../logger'; +import Issue from './Issue'; import { MediaRequest } from './MediaRequest'; import SeasonRequest from './SeasonRequest'; import { UserPushSubscription } from './UserPushSubscription'; @@ -115,6 +116,9 @@ export class User { @OneToMany(() => UserPushSubscription, (pushSub) => pushSub.user) public pushSubscriptions: UserPushSubscription[]; + @OneToMany(() => Issue, (issue) => issue.createdBy, { cascade: true }) + public createdIssues: Issue[]; + @CreateDateColumn() public createdAt: Date; diff --git a/server/entity/UserSettings.ts b/server/entity/UserSettings.ts index 02f391112..08397b12f 100644 --- a/server/entity/UserSettings.ts +++ b/server/entity/UserSettings.ts @@ -42,6 +42,15 @@ export class UserSettings { @Column({ nullable: true }) public discordId?: string; + @Column({ nullable: true }) + public pushbulletAccessToken?: string; + + @Column({ nullable: true }) + public pushoverApplicationToken?: string; + + @Column({ nullable: true }) + public pushoverUserKey?: string; + @Column({ nullable: true }) public telegramChatId?: string; diff --git a/server/index.ts b/server/index.ts index f85b02752..24c007f2a 100644 --- a/server/index.ts +++ b/server/index.ts @@ -63,11 +63,12 @@ app }); if (admin) { - const plexapi = new PlexAPI({ plexToken: admin.plexToken }); - await plexapi.syncLibraries(); - logger.info('Migrating libraries to include media type', { + logger.info('Migrating Plex libraries to include media type', { label: 'Settings', }); + + const plexapi = new PlexAPI({ plexToken: admin.plexToken }); + await plexapi.syncLibraries(); } } @@ -138,6 +139,9 @@ app saveUninitialized: false, cookie: { maxAge: 1000 * 60 * 60 * 24 * 30, + httpOnly: true, + sameSite: true, + secure: 'auto', }, store: new TypeormStore({ cleanupLimit: 2, diff --git a/server/interfaces/api/issueInterfaces.ts b/server/interfaces/api/issueInterfaces.ts new file mode 100644 index 000000000..bd17f1958 --- /dev/null +++ b/server/interfaces/api/issueInterfaces.ts @@ -0,0 +1,6 @@ +import Issue from '../../entity/Issue'; +import { PaginatedResponse } from './common'; + +export interface IssueResultsResponse extends PaginatedResponse { + results: Issue[]; +} diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 924023d43..336bab0bd 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -3,7 +3,7 @@ import type { PaginatedResponse } from './common'; export type LogMessage = { timestamp: string; level: string; - label: string; + label?: string; message: string; data?: Record; }; diff --git a/server/interfaces/api/userSettingsInterfaces.ts b/server/interfaces/api/userSettingsInterfaces.ts index 18e3c7aba..0f743efef 100644 --- a/server/interfaces/api/userSettingsInterfaces.ts +++ b/server/interfaces/api/userSettingsInterfaces.ts @@ -22,6 +22,9 @@ export interface UserSettingsNotificationsResponse { discordEnabled?: boolean; discordEnabledTypes?: number; discordId?: string; + pushbulletAccessToken?: string; + pushoverApplicationToken?: string; + pushoverUserKey?: string; telegramEnabled?: boolean; telegramBotUsername?: string; telegramChatId?: string; diff --git a/server/lib/cache.ts b/server/lib/cache.ts index fa03783c8..7782a05a8 100644 --- a/server/lib/cache.ts +++ b/server/lib/cache.ts @@ -40,7 +40,7 @@ class Cache { class CacheManager { private availableCaches: Record = { - tmdb: new Cache('tmdb', 'TMDb API', { + tmdb: new Cache('tmdb', 'The Movie Database API', { stdTtl: 21600, checkPeriod: 60 * 30, }), @@ -54,7 +54,7 @@ class CacheManager { stdTtl: 21600, checkPeriod: 60 * 30, }), - plexguid: new Cache('plexguid', 'Plex GUID Cache', { + plexguid: new Cache('plexguid', 'Plex GUID', { stdTtl: 86400 * 7, // 1 week cache checkPeriod: 60 * 30, }), diff --git a/server/lib/downloadtracker.ts b/server/lib/downloadtracker.ts index 33282285e..c62e189d8 100644 --- a/server/lib/downloadtracker.ts +++ b/server/lib/downloadtracker.ts @@ -76,23 +76,32 @@ class DownloadTracker { url: RadarrAPI.buildUrl(server, '/api/v3'), }); - const queueItems = await radarr.getQueue(); + try { + const queueItems = await radarr.getQueue(); - this.radarrServers[server.id] = queueItems.map((item) => ({ - externalId: item.movieId, - estimatedCompletionTime: new Date(item.estimatedCompletionTime), - mediaType: MediaType.MOVIE, - size: item.size, - sizeLeft: item.sizeleft, - status: item.status, - timeLeft: item.timeleft, - title: item.title, - })); + this.radarrServers[server.id] = queueItems.map((item) => ({ + externalId: item.movieId, + estimatedCompletionTime: new Date(item.estimatedCompletionTime), + mediaType: MediaType.MOVIE, + size: item.size, + sizeLeft: item.sizeleft, + status: item.status, + timeLeft: item.timeleft, + title: item.title, + })); - if (queueItems.length > 0) { - logger.debug( - `Found ${queueItems.length} item(s) in progress on Radarr server: ${server.name}`, - { label: 'Download Tracker' } + if (queueItems.length > 0) { + logger.debug( + `Found ${queueItems.length} item(s) in progress on Radarr server: ${server.name}`, + { label: 'Download Tracker' } + ); + } + } catch { + logger.error( + `Unable to get queue from Radarr server: ${server.name}`, + { + label: 'Download Tracker', + } ); } @@ -134,42 +143,51 @@ class DownloadTracker { ); }); - // Load downloads from Radarr servers + // Load downloads from Sonarr servers Promise.all( filteredServers.map(async (server) => { if (server.syncEnabled) { - const radarr = new SonarrAPI({ + const sonarr = new SonarrAPI({ apiKey: server.apiKey, url: SonarrAPI.buildUrl(server, '/api/v3'), }); - const queueItems = await radarr.getQueue(); + try { + const queueItems = await sonarr.getQueue(); - this.sonarrServers[server.id] = queueItems.map((item) => ({ - externalId: item.seriesId, - estimatedCompletionTime: new Date(item.estimatedCompletionTime), - mediaType: MediaType.TV, - size: item.size, - sizeLeft: item.sizeleft, - status: item.status, - timeLeft: item.timeleft, - title: item.title, - })); + this.sonarrServers[server.id] = queueItems.map((item) => ({ + externalId: item.seriesId, + estimatedCompletionTime: new Date(item.estimatedCompletionTime), + mediaType: MediaType.TV, + size: item.size, + sizeLeft: item.sizeleft, + status: item.status, + timeLeft: item.timeleft, + title: item.title, + })); - if (queueItems.length > 0) { - logger.debug( - `Found ${queueItems.length} item(s) in progress on Sonarr server: ${server.name}`, - { label: 'Download Tracker' } + if (queueItems.length > 0) { + logger.debug( + `Found ${queueItems.length} item(s) in progress on Sonarr server: ${server.name}`, + { label: 'Download Tracker' } + ); + } + } catch { + logger.error( + `Unable to get queue from Sonarr server: ${server.name}`, + { + label: 'Download Tracker', + } ); } // Duplicate this data to matching servers const matchingServers = settings.sonarr.filter( - (rs) => - rs.hostname === server.hostname && - rs.port === server.port && - rs.baseUrl === server.baseUrl && - rs.id !== server.id + (ss) => + ss.hostname === server.hostname && + ss.port === server.port && + ss.baseUrl === server.baseUrl && + ss.id !== server.id ); if (matchingServers.length > 0) { diff --git a/server/lib/email/openpgpEncrypt.ts b/server/lib/email/openpgpEncrypt.ts index 263f2b1f2..0e5fc8729 100644 --- a/server/lib/email/openpgpEncrypt.ts +++ b/server/lib/email/openpgpEncrypt.ts @@ -51,11 +51,12 @@ class PGPEncryptor extends Transform { // Only sign the message if private key and password exist if (this._signingKey && this._password) { - privateKey = await openpgp.readPrivateKey({ - armoredKey: this._signingKey, + privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readPrivateKey({ + armoredKey: this._signingKey, + }), + passphrase: this._password, }); - - await openpgp.decryptKey({ privateKey, passphrase: this._password }); } const emailPartDelimiter = '\r\n\r\n'; diff --git a/server/lib/notifications/agents/agent.ts b/server/lib/notifications/agents/agent.ts index 66c52a16e..edfa1262d 100644 --- a/server/lib/notifications/agents/agent.ts +++ b/server/lib/notifications/agents/agent.ts @@ -1,17 +1,23 @@ import { Notification } from '..'; +import type Issue from '../../../entity/Issue'; +import IssueComment from '../../../entity/IssueComment'; import Media from '../../../entity/Media'; import { MediaRequest } from '../../../entity/MediaRequest'; import { User } from '../../../entity/User'; import { NotificationAgentConfig } from '../../settings'; export interface NotificationPayload { + event?: string; subject: string; + notifyAdmin: boolean; notifyUser?: User; media?: Media; image?: string; message?: string; extra?: { name: string; value: string }[]; request?: MediaRequest; + issue?: Issue; + comment?: IssueComment; } export abstract class BaseAgent { diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index 97be2cba5..bd07c4de1 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -1,9 +1,13 @@ import axios from 'axios'; import { getRepository } from 'typeorm'; -import { hasNotificationType, Notification } from '..'; +import { + hasNotificationType, + Notification, + shouldSendAdminNotification, +} from '..'; +import { IssueStatus, IssueTypeName } from '../../../constants/issue'; import { User } from '../../../entity/User'; import logger from '../../../logger'; -import { Permission } from '../../permissions'; import { getSettings, NotificationAgentDiscord, @@ -107,9 +111,9 @@ class DiscordAgent type: Notification, payload: NotificationPayload ): DiscordRichEmbed { - const settings = getSettings(); - let color = EmbedColors.DARK_PURPLE; + const { applicationUrl } = getSettings().main; + let color = EmbedColors.DARK_PURPLE; const fields: Field[] = []; if (payload.request) { @@ -118,56 +122,94 @@ class DiscordAgent value: payload.request.requestedBy.displayName, inline: true, }); + + let status = ''; + switch (type) { + case Notification.MEDIA_PENDING: + color = EmbedColors.ORANGE; + status = 'Pending Approval'; + break; + case Notification.MEDIA_APPROVED: + case Notification.MEDIA_AUTO_APPROVED: + color = EmbedColors.PURPLE; + status = 'Processing'; + break; + case Notification.MEDIA_AVAILABLE: + color = EmbedColors.GREEN; + status = 'Available'; + break; + case Notification.MEDIA_DECLINED: + color = EmbedColors.RED; + status = 'Declined'; + break; + case Notification.MEDIA_FAILED: + color = EmbedColors.RED; + status = 'Failed'; + break; + } + + if (status) { + fields.push({ + name: 'Request Status', + value: status, + inline: true, + }); + } + } else if (payload.comment) { + fields.push({ + name: `Comment from ${payload.comment.user.displayName}`, + value: payload.comment.message, + inline: false, + }); + } else if (payload.issue) { + fields.push( + { + name: 'Reported By', + value: payload.issue.createdBy.displayName, + inline: true, + }, + { + name: 'Issue Type', + value: IssueTypeName[payload.issue.issueType], + inline: true, + }, + { + name: 'Issue Status', + value: + payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved', + inline: true, + } + ); + + switch (type) { + case Notification.ISSUE_CREATED: + case Notification.ISSUE_REOPENED: + color = EmbedColors.RED; + break; + case Notification.ISSUE_COMMENT: + color = EmbedColors.ORANGE; + break; + case Notification.ISSUE_RESOLVED: + color = EmbedColors.GREEN; + break; + } } - switch (type) { - case Notification.MEDIA_PENDING: - color = EmbedColors.ORANGE; - fields.push({ - name: 'Status', - value: 'Pending Approval', - inline: true, - }); - break; - case Notification.MEDIA_APPROVED: - case Notification.MEDIA_AUTO_APPROVED: - color = EmbedColors.PURPLE; - fields.push({ - name: 'Status', - value: 'Processing', - inline: true, - }); - break; - case Notification.MEDIA_AVAILABLE: - color = EmbedColors.GREEN; - fields.push({ - name: 'Status', - value: 'Available', - inline: true, - }); - break; - case Notification.MEDIA_DECLINED: - color = EmbedColors.RED; - fields.push({ - name: 'Status', - value: 'Declined', - inline: true, - }); - break; - case Notification.MEDIA_FAILED: - color = EmbedColors.RED; - fields.push({ - name: 'Status', - value: 'Failed', - inline: true, - }); - break; + for (const extra of payload.extra ?? []) { + fields.push({ + name: extra.name, + value: extra.value, + inline: true, + }); } - const url = - settings.main.applicationUrl && payload.media - ? `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}` - : undefined; + const url = applicationUrl + ? payload.issue + ? `${applicationUrl}/issues/${payload.issue.id}` + : payload.media + ? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}` + : undefined + : undefined; return { title: payload.subject, @@ -175,18 +217,12 @@ class DiscordAgent description: payload.message, color, timestamp: new Date().toISOString(), - author: { - name: settings.main.applicationTitle, - url: settings.main.applicationUrl, - }, - fields: [ - ...fields, - // If we have extra data, map it to fields for discord notifications - ...(payload.extra ?? []).map((extra) => ({ - name: extra.name, - value: extra.value, - })), - ], + author: payload.event + ? { + name: payload.event, + } + : undefined, + fields, thumbnail: { url: payload.image, }, @@ -219,54 +255,53 @@ class DiscordAgent subject: payload.subject, }); - let content = undefined; + const userMentions: string[] = []; try { if (payload.notifyUser) { - // Mention user who submitted the request if ( payload.notifyUser.settings?.hasNotificationType( NotificationAgentKey.DISCORD, type ) && - payload.notifyUser.settings?.discordId + payload.notifyUser.settings.discordId ) { - content = `<@${payload.notifyUser.settings.discordId}>`; + userMentions.push(`<@${payload.notifyUser.settings.discordId}>`); } - } else { - // Mention all users with the Manage Requests permission + } + + if (payload.notifyAdmin) { const userRepository = getRepository(User); const users = await userRepository.find(); - content = users - .filter( - (user) => - user.hasPermission(Permission.MANAGE_REQUESTS) && - user.settings?.hasNotificationType( - NotificationAgentKey.DISCORD, - type - ) && - user.settings?.discordId && - // Check if it's the user's own auto-approved request - (type !== Notification.MEDIA_AUTO_APPROVED || - user.id !== payload.request?.requestedBy.id) - ) - .map((user) => `<@${user.settings?.discordId}>`) - .join(' '); + userMentions.push( + ...users + .filter( + (user) => + user.settings?.hasNotificationType( + NotificationAgentKey.DISCORD, + type + ) && + user.settings.discordId && + shouldSendAdminNotification(type, user, payload) + ) + .map((user) => `<@${user.settings?.discordId}>`) + ); } await axios.post(settings.options.webhookUrl, { - username: settings.options.botUsername, + username: settings.options.botUsername + ? settings.options.botUsername + : getSettings().main.applicationTitle, avatar_url: settings.options.botAvatarUrl, embeds: [this.buildEmbed(type, payload)], - content, + content: userMentions.join(' '), } as DiscordWebhookPayload); return true; } catch (e) { logger.error('Error sending Discord notification', { label: 'Notifications', - mentions: content, type: Notification[type], subject: payload.subject, errorMessage: e.message, diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts index 7cf45b47f..a1dd7e4e4 100644 --- a/server/lib/notifications/agents/email.ts +++ b/server/lib/notifications/agents/email.ts @@ -1,12 +1,12 @@ import { EmailOptions } from 'email-templates'; import path from 'path'; import { getRepository } from 'typeorm'; -import { Notification } from '..'; +import { Notification, shouldSendAdminNotification } from '..'; +import { IssueType, IssueTypeName } from '../../../constants/issue'; import { MediaType } from '../../../constants/media'; import { User } from '../../../entity/User'; import logger from '../../../logger'; import PreparedEmail from '../../email'; -import { Permission } from '../../permissions'; import { getSettings, NotificationAgentEmail, @@ -67,59 +67,47 @@ class EmailAgent }; } - if (payload.media) { - let requestType = ''; + const mediaType = payload.media + ? payload.media.mediaType === MediaType.MOVIE + ? 'movie' + : 'series' + : undefined; + const is4k = payload.request?.is4k; + + if (payload.request) { let body = ''; switch (type) { case Notification.MEDIA_PENDING: - requestType = `New ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - body = `A user has requested a new ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - }!`; + body = `A new request for the following ${mediaType} ${ + is4k ? 'in 4K ' : '' + }is pending approval:`; break; case Notification.MEDIA_APPROVED: - requestType = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Approved`; - body = `Your request for the following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } has been approved:`; + body = `Your request for the following ${mediaType} ${ + is4k ? 'in 4K ' : '' + }has been approved:`; break; case Notification.MEDIA_AUTO_APPROVED: - requestType = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Automatically Approved`; - body = `A new request for the following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } has been automatically approved:`; + body = `A new request for the following ${mediaType} ${ + is4k ? 'in 4K ' : '' + }has been automatically approved:`; break; case Notification.MEDIA_AVAILABLE: - requestType = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Now Available`; - body = `The following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } you requested is now available!`; + body = `Your request for the following ${mediaType} ${ + is4k ? 'in 4K ' : '' + }is now available:`; break; case Notification.MEDIA_DECLINED: - requestType = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Declined`; - body = `Your request for the following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } was declined:`; + body = `Your request for the following ${mediaType} ${ + is4k ? 'in 4K ' : '' + }was declined:`; break; case Notification.MEDIA_FAILED: - requestType = `Failed ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - body = `A new request for the following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } could not be added to ${ - payload.media?.mediaType === MediaType.TV ? 'Sonarr' : 'Radarr' + body = `A request for the following ${mediaType} ${ + is4k ? 'in 4K ' : '' + }failed to be added to ${ + payload.media?.mediaType === MediaType.MOVIE ? 'Radarr' : 'Sonarr' }:`; break; } @@ -133,14 +121,13 @@ class EmailAgent to: recipientEmail, }, locals: { - requestType, + event: payload.event, body, mediaName: payload.subject, - mediaPlot: payload.message, mediaExtra: payload.extra ?? [], imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.request?.requestedBy.displayName, + requestedBy: payload.request.requestedBy.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -150,6 +137,52 @@ class EmailAgent recipientEmail, }, }; + } else if (payload.issue) { + const issueType = + payload.issue && payload.issue.issueType !== IssueType.OTHER + ? `${IssueTypeName[payload.issue.issueType].toLowerCase()} issue` + : 'issue'; + + let body = ''; + + switch (type) { + case Notification.ISSUE_CREATED: + body = `A new ${issueType} has been reported by ${payload.issue.createdBy.displayName} for the ${mediaType} ${payload.subject}:`; + break; + case Notification.ISSUE_COMMENT: + body = `${payload.comment?.user.displayName} commented on the ${issueType} for the ${mediaType} ${payload.subject}:`; + break; + case Notification.ISSUE_RESOLVED: + body = `The ${issueType} for the ${mediaType} ${payload.subject} was marked as resolved by ${payload.issue.modifiedBy?.displayName}!`; + break; + case Notification.ISSUE_REOPENED: + body = `The ${issueType} for the ${mediaType} ${payload.subject} was reopened by ${payload.issue.modifiedBy?.displayName}.`; + break; + } + + return { + template: path.join(__dirname, '../../../templates/email/media-issue'), + message: { + to: recipientEmail, + }, + locals: { + event: payload.event, + body, + issueDescription: payload.message, + issueComment: payload.comment?.message, + mediaName: payload.subject, + extra: payload.extra ?? [], + imageUrl: payload.image, + timestamp: new Date().toTimeString(), + actionUrl: applicationUrl + ? `${applicationUrl}/issues/${payload.issue.id}` + : undefined, + applicationUrl, + applicationTitle, + recipientName, + recipientEmail, + }, + }; } return undefined; @@ -160,7 +193,6 @@ class EmailAgent payload: NotificationPayload ): Promise { if (payload.notifyUser) { - // Send notification to the user who submitted the request if ( !payload.notifyUser.settings || // Check if user has email notifications enabled and fallback to true if undefined @@ -203,8 +235,9 @@ class EmailAgent return false; } } - } else { - // Send notifications to all users with the Manage Requests permission + } + + if (payload.notifyAdmin) { const userRepository = getRepository(User); const users = await userRepository.find(); @@ -212,7 +245,6 @@ class EmailAgent users .filter( (user) => - user.hasPermission(Permission.MANAGE_REQUESTS) && (!user.settings || // Check if user has email notifications enabled and fallback to true if undefined // since email should default to true @@ -221,9 +253,7 @@ class EmailAgent type ) ?? true)) && - // Check if it's the user's own auto-approved request - (type !== Notification.MEDIA_AUTO_APPROVED || - user.id !== payload.request?.requestedBy.id) + shouldSendAdminNotification(type, user, payload) ) .map(async (user) => { logger.debug('Sending email notification', { diff --git a/server/lib/notifications/agents/lunasea.ts b/server/lib/notifications/agents/lunasea.ts index 5d5c5216a..0269e2600 100644 --- a/server/lib/notifications/agents/lunasea.ts +++ b/server/lib/notifications/agents/lunasea.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import { hasNotificationType, Notification } from '..'; +import { IssueStatus, IssueType } from '../../../constants/issue'; import { MediaStatus } from '../../../constants/media'; import logger from '../../../logger'; import { getSettings, NotificationAgentLunaSea } from '../../settings'; @@ -22,17 +23,17 @@ class LunaSeaAgent private buildPayload(type: Notification, payload: NotificationPayload) { return { notification_type: Notification[type], + event: payload.event, subject: payload.subject, message: payload.message, image: payload.image ?? null, email: payload.notifyUser?.email, - username: payload.notifyUser?.username, + username: payload.notifyUser?.displayName, avatar: payload.notifyUser?.avatar, media: payload.media ? { media_type: payload.media.mediaType, tmdbId: payload.media.tmdbId, - imdbId: payload.media.imdbId, tvdbId: payload.media.tvdbId, status: MediaStatus[payload.media.status], status4k: MediaStatus[payload.media.status4k], @@ -47,6 +48,24 @@ class LunaSeaAgent requestedBy_avatar: payload.request.requestedBy.avatar, } : null, + issue: payload.issue + ? { + issue_id: payload.issue.id, + issue_type: IssueType[payload.issue.issueType], + issue_status: IssueStatus[payload.issue.status], + createdBy_email: payload.issue.createdBy.email, + createdBy_username: payload.issue.createdBy.displayName, + createdBy_avatar: payload.issue.createdBy.avatar, + } + : null, + comment: payload.comment + ? { + comment_message: payload.comment.message, + commentedBy_email: payload.comment.user.email, + commentedBy_username: payload.comment.user.displayName, + commentedBy_avatar: payload.comment.user.avatar, + } + : null, }; } diff --git a/server/lib/notifications/agents/pushbullet.ts b/server/lib/notifications/agents/pushbullet.ts index 160eed87f..092722c9f 100644 --- a/server/lib/notifications/agents/pushbullet.ts +++ b/server/lib/notifications/agents/pushbullet.ts @@ -1,11 +1,22 @@ import axios from 'axios'; -import { hasNotificationType, Notification } from '..'; -import { MediaType } from '../../../constants/media'; +import { getRepository } from 'typeorm'; +import { + hasNotificationType, + Notification, + shouldSendAdminNotification, +} from '..'; +import { IssueStatus, IssueTypeName } from '../../../constants/issue'; +import { User } from '../../../entity/User'; import logger from '../../../logger'; -import { getSettings, NotificationAgentPushbullet } from '../../settings'; +import { + getSettings, + NotificationAgentKey, + NotificationAgentPushbullet, +} from '../../settings'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; interface PushbulletPayload { + type: string; title: string; body: string; } @@ -25,109 +36,62 @@ class PushbulletAgent } public shouldSend(): boolean { - const settings = this.getSettings(); - - if (settings.enabled && settings.options.accessToken) { - return true; - } - - return false; + return true; } - private constructMessageDetails( + private getNotificationPayload( type: Notification, payload: NotificationPayload - ): { - title: string; - body: string; - } { - let messageTitle = ''; - let message = ''; + ): PushbulletPayload { + const title = payload.event + ? `${payload.event} - ${payload.subject}` + : payload.subject; + let body = payload.message ?? ''; - const title = payload.subject; - const plot = payload.message; - const username = payload.request?.requestedBy.displayName; + if (payload.request) { + body += `\n\nRequested By: ${payload.request.requestedBy.displayName}`; - switch (type) { - case Notification.MEDIA_PENDING: - messageTitle = `New ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - message += `${title}`; - if (plot) { - message += `\n\n${plot}`; - } - message += `\n\nRequested By: ${username}`; - message += `\nStatus: Pending Approval`; - break; - case Notification.MEDIA_APPROVED: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Approved`; - message += `${title}`; - if (plot) { - message += `\n\n${plot}`; - } - message += `\n\nRequested By: ${username}`; - message += `\nStatus: Processing`; - break; - case Notification.MEDIA_AUTO_APPROVED: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Automatically Approved`; - message += `${title}`; - if (plot) { - message += `\n\n${plot}`; - } - message += `\n\nRequested By: ${username}`; - message += `\nStatus: Processing`; - break; - case Notification.MEDIA_AVAILABLE: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Now Available`; - message += `${title}`; - if (plot) { - message += `\n\n${plot}`; - } - message += `\n\nRequested By: ${username}`; - message += `\nStatus: Available`; - break; - case Notification.MEDIA_DECLINED: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Declined`; - message += `${title}`; - if (plot) { - message += `\n\n${plot}`; - } - message += `\n\nRequested By: ${username}`; - message += `\nStatus: Declined`; - break; - case Notification.MEDIA_FAILED: - messageTitle = `Failed ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - message += `${title}`; - if (plot) { - message += `\n\n${plot}`; - } - message += `\n\nRequested By: ${username}`; - message += `\nStatus: Failed`; - break; - case Notification.TEST_NOTIFICATION: - messageTitle = 'Test Notification'; - message += `${plot}`; - break; + let status = ''; + switch (type) { + case Notification.MEDIA_PENDING: + status = 'Pending Approval'; + break; + case Notification.MEDIA_APPROVED: + case Notification.MEDIA_AUTO_APPROVED: + status = 'Processing'; + break; + case Notification.MEDIA_AVAILABLE: + status = 'Available'; + break; + case Notification.MEDIA_DECLINED: + status = 'Declined'; + break; + case Notification.MEDIA_FAILED: + status = 'Failed'; + break; + } + + if (status) { + body += `\nRequest Status: ${status}`; + } + } else if (payload.comment) { + body += `\n\nComment from ${payload.comment.user.displayName}:\n${payload.comment.message}`; + } else if (payload.issue) { + body += `\n\nReported By: ${payload.issue.createdBy.displayName}`; + body += `\nIssue Type: ${IssueTypeName[payload.issue.issueType]}`; + body += `\nIssue Status: ${ + payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved' + }`; } for (const extra of payload.extra ?? []) { - message += `\n${extra.name}: ${extra.value}`; + body += `\n${extra.name}: ${extra.value}`; } return { - title: messageTitle, - body: message, + type: 'note', + title, + body, }; } @@ -136,46 +100,128 @@ class PushbulletAgent payload: NotificationPayload ): Promise { const settings = this.getSettings(); + const endpoint = 'https://api.pushbullet.com/v2/pushes'; + const notificationPayload = this.getNotificationPayload(type, payload); - if (!hasNotificationType(type, settings.types ?? 0)) { - return true; - } - - logger.debug('Sending Pushbullet notification', { - label: 'Notifications', - type: Notification[type], - subject: payload.subject, - }); - - try { - const { title, body } = this.constructMessageDetails(type, payload); - - await axios.post( - 'https://api.pushbullet.com/v2/pushes', - { - type: 'note', - title: title, - body: body, - } as PushbulletPayload, - { - headers: { - 'Access-Token': settings.options.accessToken, - }, - } - ); - - return true; - } catch (e) { - logger.error('Error sending Pushbullet notification', { + // Send system notification + if ( + hasNotificationType(type, settings.types ?? 0) && + settings.enabled && + settings.options.accessToken + ) { + logger.debug('Sending Pushbullet notification', { label: 'Notifications', type: Notification[type], subject: payload.subject, - errorMessage: e.message, - response: e.response?.data, }); - return false; + try { + await axios.post(endpoint, notificationPayload, { + headers: { + 'Access-Token': settings.options.accessToken, + }, + }); + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } } + + if (payload.notifyUser) { + if ( + payload.notifyUser.settings?.hasNotificationType( + NotificationAgentKey.PUSHBULLET, + type + ) && + payload.notifyUser.settings?.pushbulletAccessToken && + payload.notifyUser.settings.pushbulletAccessToken !== + settings.options.accessToken + ) { + logger.debug('Sending Pushbullet notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, notificationPayload, { + headers: { + 'Access-Token': payload.notifyUser.settings.pushbulletAccessToken, + }, + }); + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + } + + if (payload.notifyAdmin) { + const userRepository = getRepository(User); + const users = await userRepository.find(); + + await Promise.all( + users + .filter( + (user) => + user.settings?.hasNotificationType( + NotificationAgentKey.PUSHBULLET, + type + ) && shouldSendAdminNotification(type, user, payload) + ) + .map(async (user) => { + if ( + user.settings?.pushbulletAccessToken && + user.settings.pushbulletAccessToken !== + settings.options.accessToken + ) { + logger.debug('Sending Pushbullet notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, notificationPayload, { + headers: { + 'Access-Token': user.settings.pushbulletAccessToken, + }, + }); + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + }) + ); + } + + return true; } } diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts index b37b54461..6c8f06dc6 100644 --- a/server/lib/notifications/agents/pushover.ts +++ b/server/lib/notifications/agents/pushover.ts @@ -1,8 +1,18 @@ import axios from 'axios'; -import { hasNotificationType, Notification } from '..'; -import { MediaType } from '../../../constants/media'; +import { getRepository } from 'typeorm'; +import { + hasNotificationType, + Notification, + shouldSendAdminNotification, +} from '..'; +import { IssueStatus, IssueTypeName } from '../../../constants/issue'; +import { User } from '../../../entity/User'; import logger from '../../../logger'; -import { getSettings, NotificationAgentPushover } from '../../settings'; +import { + getSettings, + NotificationAgentKey, + NotificationAgentPushover, +} from '../../settings'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; interface PushoverPayload { @@ -31,130 +41,89 @@ class PushoverAgent } public shouldSend(): boolean { - const settings = this.getSettings(); - - if ( - settings.enabled && - settings.options.accessToken && - settings.options.userToken - ) { - return true; - } - - return false; + return true; } - private constructMessageDetails( + private getNotificationPayload( type: Notification, payload: NotificationPayload - ): { - title: string; - message: string; - url: string | undefined; - url_title: string | undefined; - priority: number; - } { - const settings = getSettings(); - let messageTitle = ''; - let message = ''; - let url: string | undefined; - let url_title: string | undefined; + ): Partial { + const { applicationUrl, applicationTitle } = getSettings().main; + + const title = payload.event ?? payload.subject; + let message = payload.event ? `${payload.subject}` : ''; let priority = 0; - const title = payload.subject; - const plot = payload.message; - const username = payload.request?.requestedBy.displayName; + if (payload.message) { + message += `${message ? '\n' : ''}${payload.message}`; + } - switch (type) { - case Notification.MEDIA_PENDING: - messageTitle = `New ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - message += `${title}`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nPending Approval`; - break; - case Notification.MEDIA_APPROVED: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Approved`; - message += `${title}`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nProcessing`; - break; - case Notification.MEDIA_AUTO_APPROVED: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Automatically Approved`; - message += `${title}`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nProcessing`; - break; - case Notification.MEDIA_AVAILABLE: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Now Available`; - message += `${title}`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nAvailable`; - break; - case Notification.MEDIA_DECLINED: - messageTitle = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Declined`; - message += `${title}`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nDeclined`; + if (payload.request) { + message += `\n\nRequested By: ${payload.request.requestedBy.displayName}`; + + let status = ''; + switch (type) { + case Notification.MEDIA_PENDING: + status = 'Pending Approval'; + break; + case Notification.MEDIA_APPROVED: + case Notification.MEDIA_AUTO_APPROVED: + status = 'Processing'; + break; + case Notification.MEDIA_AVAILABLE: + status = 'Available'; + break; + case Notification.MEDIA_DECLINED: + status = 'Declined'; + priority = 1; + break; + case Notification.MEDIA_FAILED: + status = 'Failed'; + priority = 1; + break; + } + + if (status) { + message += `\nRequest Status: ${status}`; + } + } else if (payload.comment) { + message += `\n\nComment from ${payload.comment.user.displayName}: ${payload.comment.message}`; + } else if (payload.issue) { + message += `\n\nReported By: ${payload.issue.createdBy.displayName}`; + message += `\nIssue Type: ${ + IssueTypeName[payload.issue.issueType] + }`; + message += `\nIssue Status: ${ + payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved' + }`; + + if (type === Notification.ISSUE_CREATED) { priority = 1; - break; - case Notification.MEDIA_FAILED: - messageTitle = `Failed ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - message += `${title}`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nFailed`; - priority = 1; - break; - case Notification.TEST_NOTIFICATION: - messageTitle = 'Test Notification'; - message += `${plot}`; - break; + } } for (const extra of payload.extra ?? []) { - message += `\n\n${extra.name}\n${extra.value}`; + message += `\n${extra.name}: ${extra.value}`; } - if (settings.main.applicationUrl && payload.media) { - url = `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`; - url_title = `Open in ${settings.main.applicationTitle}`; - } + const url = applicationUrl + ? payload.issue + ? `${applicationUrl}/issues/${payload.issue.id}` + : payload.media + ? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}` + : undefined + : undefined; + const url_title = url + ? `View ${payload.issue ? 'Issue' : 'Media'} in ${applicationTitle}` + : undefined; return { - title: messageTitle, + title, message, url, url_title, priority, + html: 1, }; } @@ -163,45 +132,134 @@ class PushoverAgent payload: NotificationPayload ): Promise { const settings = this.getSettings(); + const endpoint = 'https://api.pushover.net/1/messages.json'; + const notificationPayload = this.getNotificationPayload(type, payload); - if (!hasNotificationType(type, settings.types ?? 0)) { - return true; - } - - logger.debug('Sending Pushover notification', { - label: 'Notifications', - type: Notification[type], - subject: payload.subject, - }); - try { - const endpoint = 'https://api.pushover.net/1/messages.json'; - - const { title, message, url, url_title, priority } = - this.constructMessageDetails(type, payload); - - await axios.post(endpoint, { - token: settings.options.accessToken, - user: settings.options.userToken, - title: title, - message: message, - url: url, - url_title: url_title, - priority: priority, - html: 1, - } as PushoverPayload); - - return true; - } catch (e) { - logger.error('Error sending Pushover notification', { + // Send system notification + if ( + hasNotificationType(type, settings.types ?? 0) && + settings.enabled && + settings.options.accessToken && + settings.options.userToken + ) { + logger.debug('Sending Pushover notification', { label: 'Notifications', type: Notification[type], subject: payload.subject, - errorMessage: e.message, - response: e.response?.data, }); - return false; + try { + await axios.post(endpoint, { + ...notificationPayload, + token: settings.options.accessToken, + user: settings.options.userToken, + } as PushoverPayload); + } catch (e) { + logger.error('Error sending Pushover notification', { + label: 'Notifications', + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } } + + if (payload.notifyUser) { + if ( + payload.notifyUser.settings?.hasNotificationType( + NotificationAgentKey.PUSHOVER, + type + ) && + payload.notifyUser.settings?.pushoverApplicationToken && + payload.notifyUser.settings?.pushoverUserKey && + payload.notifyUser.settings.pushoverApplicationToken !== + settings.options.accessToken && + payload.notifyUser.settings?.pushoverUserKey !== + settings.options.userToken + ) { + logger.debug('Sending Pushover notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, { + ...notificationPayload, + token: payload.notifyUser.settings.pushoverApplicationToken, + user: payload.notifyUser.settings.pushoverUserKey, + } as PushoverPayload); + } catch (e) { + logger.error('Error sending Pushover notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + } + + if (payload.notifyAdmin) { + const userRepository = getRepository(User); + const users = await userRepository.find(); + + await Promise.all( + users + .filter( + (user) => + user.settings?.hasNotificationType( + NotificationAgentKey.PUSHOVER, + type + ) && shouldSendAdminNotification(type, user, payload) + ) + .map(async (user) => { + if ( + user.settings?.pushoverApplicationToken && + user.settings?.pushoverUserKey && + user.settings.pushoverApplicationToken !== + settings.options.accessToken && + user.settings.pushoverUserKey !== settings.options.userToken + ) { + logger.debug('Sending Pushover notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, { + ...notificationPayload, + token: user.settings.pushoverApplicationToken, + user: user.settings.pushoverUserKey, + } as PushoverPayload); + } catch (e) { + logger.error('Error sending Pushover notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + }) + ); + } + + return true; } } diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts index 8065f9a63..eede9f094 100644 --- a/server/lib/notifications/agents/slack.ts +++ b/server/lib/notifications/agents/slack.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { hasNotificationType, Notification } from '..'; -import { MediaType } from '../../../constants/media'; +import { IssueStatus, IssueTypeName } from '../../../constants/issue'; import logger from '../../../logger'; import { getSettings, NotificationAgentSlack } from '../../settings'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; @@ -19,9 +19,10 @@ interface TextItem { interface Element { type: 'button'; text?: TextItem; - value: string; - url: string; - action_id: 'button-action'; + action_id: string; + url?: string; + value?: string; + style?: 'primary' | 'danger'; } interface EmbedBlock { @@ -34,7 +35,7 @@ interface EmbedBlock { image_url: string; alt_text: string; }; - elements?: Element[]; + elements?: (Element | TextItem)[]; } interface SlackBlockEmbed { @@ -59,9 +60,7 @@ class SlackAgent type: Notification, payload: NotificationPayload ): SlackBlockEmbed { - const settings = getSettings(); - let header = ''; - let actionUrl: string | undefined; + const { applicationUrl, applicationTitle } = getSettings().main; const fields: EmbedField[] = []; @@ -70,66 +69,55 @@ class SlackAgent type: 'mrkdwn', text: `*Requested By*\n${payload.request.requestedBy.displayName}`, }); - } - switch (type) { - case Notification.MEDIA_PENDING: - header = `New ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; + let status = ''; + switch (type) { + case Notification.MEDIA_PENDING: + status = 'Pending Approval'; + break; + case Notification.MEDIA_APPROVED: + case Notification.MEDIA_AUTO_APPROVED: + status = 'Processing'; + break; + case Notification.MEDIA_AVAILABLE: + status = 'Available'; + break; + case Notification.MEDIA_DECLINED: + status = 'Declined'; + break; + case Notification.MEDIA_FAILED: + status = 'Failed'; + break; + } + + if (status) { fields.push({ type: 'mrkdwn', - text: '*Status*\nPending Approval', + text: `*Request Status*\n${status}`, }); - break; - case Notification.MEDIA_APPROVED: - header = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Approved`; - fields.push({ + } + } else if (payload.comment) { + fields.push({ + type: 'mrkdwn', + text: `*Comment from ${payload.comment.user.displayName}*\n${payload.comment.message}`, + }); + } else if (payload.issue) { + fields.push( + { type: 'mrkdwn', - text: '*Status*\nProcessing', - }); - break; - case Notification.MEDIA_AUTO_APPROVED: - header = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Automatically Approved`; - fields.push({ + text: `*Reported By*\n${payload.issue.createdBy.displayName}`, + }, + { type: 'mrkdwn', - text: '*Status*\nProcessing', - }); - break; - case Notification.MEDIA_AVAILABLE: - header = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Now Available`; - fields.push({ + text: `*Issue Type*\n${IssueTypeName[payload.issue.issueType]}`, + }, + { type: 'mrkdwn', - text: '*Status*\nAvailable', - }); - break; - case Notification.MEDIA_DECLINED: - header = `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Declined`; - fields.push({ - type: 'mrkdwn', - text: '*Status*\nDeclined', - }); - break; - case Notification.MEDIA_FAILED: - header = `Failed ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request`; - fields.push({ - type: 'mrkdwn', - text: '*Status*\nFailed', - }); - break; - case Notification.TEST_NOTIFICATION: - header = 'Test Notification'; - break; + text: `*Issue Status*\n${ + payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved' + }`, + } + ); } for (const extra of payload.extra ?? []) { @@ -139,30 +127,28 @@ class SlackAgent }); } - if (settings.main.applicationUrl && payload.media) { - actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`; - } + const blocks: EmbedBlock[] = []; - const blocks: EmbedBlock[] = [ - { - type: 'header', - text: { - type: 'plain_text', - text: header, - }, - }, - ]; - - if (type !== Notification.TEST_NOTIFICATION) { + if (payload.event) { blocks.push({ - type: 'section', - text: { - type: 'mrkdwn', - text: `*${payload.subject}*`, - }, + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: `*${payload.event}*`, + }, + ], }); } + blocks.push({ + type: 'header', + text: { + type: 'plain_text', + text: payload.subject, + }, + }); + if (payload.message) { blocks.push({ type: 'section', @@ -183,30 +169,31 @@ class SlackAgent if (fields.length > 0) { blocks.push({ type: 'section', - fields: [ - ...fields, - ...(payload.extra ?? []).map( - (extra): EmbedField => ({ - type: 'mrkdwn', - text: `*${extra.name}*\n${extra.value}`, - }) - ), - ], + fields, }); } - if (actionUrl) { + const url = applicationUrl + ? payload.issue + ? `${applicationUrl}/issues/${payload.issue.id}` + : payload.media + ? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}` + : undefined + : undefined; + + if (url) { blocks.push({ type: 'actions', elements: [ { - action_id: 'button-action', + action_id: 'open-in-overseerr', type: 'button', - url: actionUrl, - value: 'open_overseerr', + url, text: { type: 'plain_text', - text: `Open in ${settings.main.applicationTitle}`, + text: `View ${ + payload.issue ? 'Issue' : 'Media' + } in ${applicationTitle}`, }, }, ], diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index b63fbd62f..3450a3c2a 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -1,10 +1,13 @@ import axios from 'axios'; import { getRepository } from 'typeorm'; -import { hasNotificationType, Notification } from '..'; -import { MediaType } from '../../../constants/media'; +import { + hasNotificationType, + Notification, + shouldSendAdminNotification, +} from '..'; +import { IssueStatus, IssueTypeName } from '../../../constants/issue'; import { User } from '../../../entity/User'; import logger from '../../../logger'; -import { Permission } from '../../permissions'; import { getSettings, NotificationAgentKey, @@ -46,11 +49,7 @@ class TelegramAgent public shouldSend(): boolean { const settings = this.getSettings(); - if ( - settings.enabled && - settings.options.botAPI && - settings.options.chatId - ) { + if (settings.enabled && settings.options.botAPI) { return true; } @@ -61,118 +60,91 @@ class TelegramAgent return text ? text.replace(/[_*[\]()~>#+=|{}.!-]/gi, (x) => '\\' + x) : ''; } - private buildMessage( + private getNotificationPayload( type: Notification, - payload: NotificationPayload, - chatId: string, - sendSilently: boolean - ): TelegramMessagePayload | TelegramPhotoPayload { - const settings = getSettings(); - let message = ''; - - const title = this.escapeText(payload.subject); - const plot = this.escapeText(payload.message); - const user = this.escapeText(payload.request?.requestedBy.displayName); - const applicationTitle = this.escapeText(settings.main.applicationTitle); + payload: NotificationPayload + ): Partial { + const { applicationUrl, applicationTitle } = getSettings().main; /* eslint-disable no-useless-escape */ - switch (type) { - case Notification.MEDIA_PENDING: - message += `\*New ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request\*`; - message += `\n\n\*${title}\*`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\n\*Requested By\*\n${user}`; - message += `\n\n\*Status\*\nPending Approval`; - break; - case Notification.MEDIA_APPROVED: - message += `\*${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Approved\*`; - message += `\n\n\*${title}\*`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\n\*Requested By\*\n${user}`; - message += `\n\n\*Status\*\nProcessing`; - break; - case Notification.MEDIA_AUTO_APPROVED: - message += `\*${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Automatically Approved\*`; - message += `\n\n\*${title}\*`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\n\*Requested By\*\n${user}`; - message += `\n\n\*Status\*\nProcessing`; - break; - case Notification.MEDIA_AVAILABLE: - message += `\*${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Now Available\*`; - message += `\n\n\*${title}\*`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\n\*Requested By\*\n${user}`; - message += `\n\n\*Status\*\nAvailable`; - break; - case Notification.MEDIA_DECLINED: - message += `\*${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Declined\*`; - message += `\n\n\*${title}\*`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\n\*Requested By\*\n${user}`; - message += `\n\n\*Status\*\nDeclined`; - break; - case Notification.MEDIA_FAILED: - message += `\*Failed ${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request\*`; - message += `\n\n\*${title}\*`; - if (plot) { - message += `\n${plot}`; - } - message += `\n\n\*Requested By\*\n${user}`; - message += `\n\n\*Status\*\nFailed`; - break; - case Notification.TEST_NOTIFICATION: - message += `\*Test Notification\*`; - message += `\n\n${plot}`; - break; + let message = `\*${this.escapeText( + payload.event ? `${payload.event} - ${payload.subject}` : payload.subject + )}\*`; + if (payload.message) { + message += `\n${this.escapeText(payload.message)}`; + } + + if (payload.request) { + message += `\n\n\*Requested By:\* ${this.escapeText( + payload.request?.requestedBy.displayName + )}`; + + let status = ''; + switch (type) { + case Notification.MEDIA_PENDING: + status = 'Pending Approval'; + break; + case Notification.MEDIA_APPROVED: + case Notification.MEDIA_AUTO_APPROVED: + status = 'Processing'; + break; + case Notification.MEDIA_AVAILABLE: + status = 'Available'; + break; + case Notification.MEDIA_DECLINED: + status = 'Declined'; + break; + case Notification.MEDIA_FAILED: + status = 'Failed'; + break; + } + + if (status) { + message += `\n\*Request Status:\* ${status}`; + } + } else if (payload.comment) { + message += `\n\n\*Comment from ${this.escapeText( + payload.comment.user.displayName + )}:\* ${this.escapeText(payload.comment.message)}`; + } else if (payload.issue) { + message += `\n\n\*Reported By:\* ${this.escapeText( + payload.issue.createdBy.displayName + )}`; + message += `\n\*Issue Type:\* ${IssueTypeName[payload.issue.issueType]}`; + message += `\n\*Issue Status:\* ${ + payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved' + }`; } for (const extra of payload.extra ?? []) { - message += `\n\n\*${extra.name}\*\n${extra.value}`; + message += `\n\*${extra.name}:\* ${extra.value}`; } - if (settings.main.applicationUrl && payload.media) { - const actionUrl = `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`; - message += `\n\n\[Open in ${applicationTitle}\]\(${actionUrl}\)`; + const url = applicationUrl + ? payload.issue + ? `${applicationUrl}/issues/${payload.issue.id}` + : payload.media + ? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}` + : undefined + : undefined; + + if (url) { + message += `\n\n\[View ${ + payload.issue ? 'Issue' : 'Media' + } in ${this.escapeText(applicationTitle)}\]\(${url}\)`; } /* eslint-enable */ return payload.image - ? ({ + ? { photo: payload.image, caption: message, parse_mode: 'MarkdownV2', - chat_id: chatId, - disable_notification: !!sendSilently, - } as TelegramPhotoPayload) - : ({ + } + : { text: message, parse_mode: 'MarkdownV2', - chat_id: chatId, - disable_notification: !!sendSilently, - } as TelegramMessagePayload); + }; } public async send( @@ -180,13 +152,16 @@ class TelegramAgent payload: NotificationPayload ): Promise { const settings = this.getSettings(); - const endpoint = `${this.baseUrl}bot${settings.options.botAPI}/${ payload.image ? 'sendPhoto' : 'sendMessage' }`; + const notificationPayload = this.getNotificationPayload(type, payload); // Send system notification - if (hasNotificationType(type, settings.types ?? 0)) { + if ( + hasNotificationType(type, settings.types ?? 0) && + settings.options.chatId + ) { logger.debug('Sending Telegram notification', { label: 'Notifications', type: Notification[type], @@ -194,15 +169,11 @@ class TelegramAgent }); try { - await axios.post( - endpoint, - this.buildMessage( - type, - payload, - settings.options.chatId, - settings.options.sendSilently - ) - ); + await axios.post(endpoint, { + ...notificationPayload, + chat_id: settings.options.chatId, + disable_notification: !!settings.options.sendSilently, + } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { label: 'Notifications', @@ -217,14 +188,13 @@ class TelegramAgent } if (payload.notifyUser) { - // Send notification to the user who submitted the request if ( payload.notifyUser.settings?.hasNotificationType( NotificationAgentKey.TELEGRAM, type ) && payload.notifyUser.settings?.telegramChatId && - payload.notifyUser.settings?.telegramChatId !== settings.options.chatId + payload.notifyUser.settings.telegramChatId !== settings.options.chatId ) { logger.debug('Sending Telegram notification', { label: 'Notifications', @@ -234,15 +204,12 @@ class TelegramAgent }); try { - await axios.post( - endpoint, - this.buildMessage( - type, - payload, - payload.notifyUser.settings.telegramChatId, - !!payload.notifyUser.settings.telegramSendSilently - ) - ); + await axios.post(endpoint, { + ...notificationPayload, + chat_id: payload.notifyUser.settings.telegramChatId, + disable_notification: + !!payload.notifyUser.settings.telegramSendSilently, + } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { label: 'Notifications', @@ -256,8 +223,9 @@ class TelegramAgent return false; } } - } else { - // Send notifications to all users with the Manage Requests permission + } + + if (payload.notifyAdmin) { const userRepository = getRepository(User); const users = await userRepository.find(); @@ -265,14 +233,10 @@ class TelegramAgent users .filter( (user) => - user.hasPermission(Permission.MANAGE_REQUESTS) && user.settings?.hasNotificationType( NotificationAgentKey.TELEGRAM, type - ) && - // Check if it's the user's own auto-approved request - (type !== Notification.MEDIA_AUTO_APPROVED || - user.id !== payload.request?.requestedBy.id) + ) && shouldSendAdminNotification(type, user, payload) ) .map(async (user) => { if ( @@ -287,15 +251,11 @@ class TelegramAgent }); try { - await axios.post( - endpoint, - this.buildMessage( - type, - payload, - user.settings.telegramChatId, - !!user.settings?.telegramSendSilently - ) - ); + await axios.post(endpoint, { + ...notificationPayload, + chat_id: user.settings.telegramChatId, + disable_notification: !!user.settings?.telegramSendSilently, + } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { label: 'Notifications', diff --git a/server/lib/notifications/agents/webhook.ts b/server/lib/notifications/agents/webhook.ts index 2959f81c3..ba2bf5e59 100644 --- a/server/lib/notifications/agents/webhook.ts +++ b/server/lib/notifications/agents/webhook.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { get } from 'lodash'; import { hasNotificationType, Notification } from '..'; +import { IssueStatus, IssueType } from '../../../constants/issue'; import { MediaStatus } from '../../../constants/media'; import logger from '../../../logger'; import { getSettings, NotificationAgentWebhook } from '../../settings'; @@ -13,6 +14,7 @@ type KeyMapFunction = ( const KeyMap: Record = { notification_type: (_payload, type) => Notification[type], + event: 'event', subject: 'subject', message: 'message', image: 'image', @@ -22,13 +24,12 @@ const KeyMap: Record = { notifyuser_settings_discordId: 'notifyUser.settings.discordId', notifyuser_settings_telegramChatId: 'notifyUser.settings.telegramChatId', media_tmdbid: 'media.tmdbId', - media_imdbid: 'media.imdbId', media_tvdbid: 'media.tvdbId', media_type: 'media.mediaType', media_status: (payload) => - payload.media?.status ? MediaStatus[payload.media?.status] : '', + payload.media ? MediaStatus[payload.media.status] : '', media_status4k: (payload) => - payload.media?.status ? MediaStatus[payload.media?.status4k] : '', + payload.media ? MediaStatus[payload.media.status4k] : '', request_id: 'request.id', requestedBy_username: 'request.requestedBy.displayName', requestedBy_email: 'request.requestedBy.email', @@ -36,6 +37,22 @@ const KeyMap: Record = { requestedBy_settings_discordId: 'request.requestedBy.settings.discordId', requestedBy_settings_telegramChatId: 'request.requestedBy.settings.telegramChatId', + issue_id: 'issue.id', + issue_type: (payload) => + payload.issue ? IssueType[payload.issue.issueType] : '', + issue_status: (payload) => + payload.issue ? IssueStatus[payload.issue.status] : '', + reportedBy_username: 'issue.createdBy.displayName', + reportedBy_email: 'issue.createdBy.email', + reportedBy_avatar: 'issue.createdBy.avatar', + reportedBy_settings_discordId: 'issue.createdBy.settings.discordId', + reportedBy_settings_telegramChatId: 'issue.createdBy.settings.telegramChatId', + comment_message: 'comment.message', + commentedBy_username: 'comment.user.displayName', + commentedBy_email: 'comment.user.email', + commentedBy_avatar: 'comment.user.avatar', + commentedBy_settings_discordId: 'comment.user.settings.discordId', + commentedBy_settings_telegramChatId: 'comment.user.settings.telegramChatId', }; class WebhookAgent @@ -78,6 +95,22 @@ class WebhookAgent } delete finalPayload[key]; key = 'request'; + } else if (key === '{{issue}}') { + if (payload.issue) { + finalPayload.issue = finalPayload[key]; + } else { + finalPayload.issue = null; + } + delete finalPayload[key]; + key = 'issue'; + } else if (key === '{{comment}}') { + if (payload.comment) { + finalPayload.comment = finalPayload[key]; + } else { + finalPayload.comment = null; + } + delete finalPayload[key]; + key = 'comment'; } if (typeof finalPayload[key] === 'string') { diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index afe4b7c10..c87d9496c 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -1,11 +1,11 @@ import { getRepository } from 'typeorm'; import webpush from 'web-push'; -import { Notification } from '..'; +import { Notification, shouldSendAdminNotification } from '..'; +import { IssueType, IssueTypeName } from '../../../constants/issue'; import { MediaType } from '../../../constants/media'; import { User } from '../../../entity/User'; import { UserPushSubscription } from '../../../entity/UserPushSubscription'; import logger from '../../../logger'; -import { Permission } from '../../permissions'; import { getSettings, NotificationAgentConfig, @@ -15,12 +15,11 @@ import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; interface PushNotificationPayload { notificationType: string; - mediaType?: 'movie' | 'tv'; - tmdbId?: number; subject: string; message?: string; image?: string; actionUrl?: string; + actionUrlTitle?: string; requestId?: number; } @@ -42,97 +41,92 @@ class WebPushAgent type: Notification, payload: NotificationPayload ): PushNotificationPayload { + const mediaType = payload.media + ? payload.media.mediaType === MediaType.MOVIE + ? 'movie' + : 'series' + : undefined; + const is4k = payload.request?.is4k; + + const issueType = payload.issue + ? payload.issue.issueType !== IssueType.OTHER + ? `${IssueTypeName[payload.issue.issueType].toLowerCase()} issue` + : 'issue' + : undefined; + + let message: string | undefined; switch (type) { - case Notification.NONE: + case Notification.TEST_NOTIFICATION: + message = payload.message; + break; + case Notification.MEDIA_APPROVED: + message = `Your ${ + is4k ? '4K ' : '' + }${mediaType} request has been approved.`; + break; + case Notification.MEDIA_AUTO_APPROVED: + message = `Automatically approved a new ${ + is4k ? '4K ' : '' + }${mediaType} request from ${ + payload.request?.requestedBy.displayName + }.`; + break; + case Notification.MEDIA_AVAILABLE: + message = `Your ${ + is4k ? '4K ' : '' + }${mediaType} request is now available!`; + break; + case Notification.MEDIA_DECLINED: + message = `Your ${is4k ? '4K ' : ''}${mediaType} request was declined.`; + break; + case Notification.MEDIA_FAILED: + message = `Failed to process ${is4k ? '4K ' : ''}${mediaType} request.`; + break; + case Notification.MEDIA_PENDING: + message = `Approval required for a new ${ + is4k ? '4K ' : '' + }${mediaType} request from ${ + payload.request?.requestedBy.displayName + }.`; + break; + case Notification.ISSUE_CREATED: + message = `A new ${issueType} was reported by ${payload.issue?.createdBy.displayName}.`; + break; + case Notification.ISSUE_COMMENT: + message = `${payload.comment?.user.displayName} commented on the ${issueType}.`; + break; + case Notification.ISSUE_RESOLVED: + message = `The ${issueType} was marked as resolved by ${payload.issue?.modifiedBy?.displayName}!`; + break; + case Notification.ISSUE_REOPENED: + message = `The ${issueType} was reopened by ${payload.issue?.modifiedBy?.displayName}.`; + break; + default: return { notificationType: Notification[type], subject: 'Unknown', }; - case Notification.TEST_NOTIFICATION: - return { - notificationType: Notification[type], - subject: payload.subject, - message: payload.message, - }; - case Notification.MEDIA_APPROVED: - return { - notificationType: Notification[type], - subject: payload.subject, - message: `Your ${ - payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series' - } request has been approved.`, - image: payload.image, - mediaType: payload.media?.mediaType, - tmdbId: payload.media?.tmdbId, - requestId: payload.request?.id, - actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, - }; - case Notification.MEDIA_AUTO_APPROVED: - return { - notificationType: Notification[type], - subject: payload.subject, - message: `Automatically approved a new ${ - payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series' - } request from ${payload.request?.requestedBy.displayName}.`, - image: payload.image, - mediaType: payload.media?.mediaType, - tmdbId: payload.media?.tmdbId, - requestId: payload.request?.id, - actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, - }; - case Notification.MEDIA_AVAILABLE: - return { - notificationType: Notification[type], - subject: payload.subject, - message: `Your ${ - payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series' - } request is now available!`, - image: payload.image, - mediaType: payload.media?.mediaType, - tmdbId: payload.media?.tmdbId, - requestId: payload.request?.id, - actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, - }; - case Notification.MEDIA_DECLINED: - return { - notificationType: Notification[type], - subject: payload.subject, - message: `Your ${ - payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series' - } request was declined.`, - image: payload.image, - mediaType: payload.media?.mediaType, - tmdbId: payload.media?.tmdbId, - requestId: payload.request?.id, - actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, - }; - case Notification.MEDIA_FAILED: - return { - notificationType: Notification[type], - subject: payload.subject, - message: `Failed to process ${ - payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series' - } request.`, - image: payload.image, - mediaType: payload.media?.mediaType, - tmdbId: payload.media?.tmdbId, - requestId: payload.request?.id, - actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, - }; - case Notification.MEDIA_PENDING: - return { - notificationType: Notification[type], - subject: payload.subject, - message: `Approval required for new ${ - payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series' - } request from ${payload.request?.requestedBy.displayName}.`, - image: payload.image, - mediaType: payload.media?.mediaType, - tmdbId: payload.media?.tmdbId, - requestId: payload.request?.id, - actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, - }; } + + const actionUrl = payload.issue + ? `/issues/${payload.issue.id}` + : payload.media + ? `/${payload.media.mediaType}/${payload.media.tmdbId}` + : undefined; + + const actionUrlTitle = actionUrl + ? `View ${payload.issue ? 'Issue' : 'Media'}` + : undefined; + + return { + notificationType: Notification[type], + subject: payload.subject, + message, + image: payload.image, + requestId: payload.request?.id, + actionUrl, + actionUrlTitle, + }; } public shouldSend(): boolean { @@ -151,7 +145,7 @@ class WebPushAgent const userPushSubRepository = getRepository(UserPushSubscription); const settings = getSettings(); - let pushSubs: UserPushSubscription[] = []; + const pushSubs: UserPushSubscription[] = []; const mainUser = await userRepository.findOne({ where: { id: 1 } }); @@ -169,13 +163,14 @@ class WebPushAgent where: { user: payload.notifyUser.id }, }); - pushSubs = notifySubs; - } else if (!payload.notifyUser) { + pushSubs.push(...notifySubs); + } + + if (payload.notifyAdmin) { const users = await userRepository.find(); const manageUsers = users.filter( (user) => - user.hasPermission(Permission.MANAGE_REQUESTS) && // Check if user has webpush notifications enabled and fallback to true if undefined // since web push should default to true (user.settings?.hasNotificationType( @@ -183,9 +178,7 @@ class WebPushAgent type ) ?? true) && - // Check if it's the user's own auto-approved request - (type !== Notification.MEDIA_AUTO_APPROVED || - user.id !== payload.request?.requestedBy.id) + shouldSendAdminNotification(type, user, payload) ); const allSubs = await userPushSubRepository @@ -196,7 +189,7 @@ class WebPushAgent }) .getMany(); - pushSubs = allSubs; + pushSubs.push(...allSubs); } if (mainUser && pushSubs.length > 0) { @@ -206,6 +199,11 @@ class WebPushAgent settings.vapidPrivate ); + const notificationPayload = Buffer.from( + JSON.stringify(this.getNotificationPayload(type, payload)), + 'utf-8' + ); + await Promise.all( pushSubs.map(async (sub) => { logger.debug('Sending web push notification', { @@ -224,10 +222,7 @@ class WebPushAgent p256dh: sub.p256dh, }, }, - Buffer.from( - JSON.stringify(this.getNotificationPayload(type, payload)), - 'utf-8' - ) + notificationPayload ); } catch (e) { logger.error( diff --git a/server/lib/notifications/index.ts b/server/lib/notifications/index.ts index a2eb01419..b8111d02f 100644 --- a/server/lib/notifications/index.ts +++ b/server/lib/notifications/index.ts @@ -1,4 +1,6 @@ +import { User } from '../../entity/User'; import logger from '../../logger'; +import { Permission } from '../permissions'; import type { NotificationAgent, NotificationPayload } from './agents/agent'; export enum Notification { @@ -10,6 +12,10 @@ export enum Notification { TEST_NOTIFICATION = 32, MEDIA_DECLINED = 64, MEDIA_AUTO_APPROVED = 128, + ISSUE_CREATED = 256, + ISSUE_COMMENT = 512, + ISSUE_RESOLVED = 1024, + ISSUE_REOPENED = 2048, } export const hasNotificationType = ( @@ -38,6 +44,50 @@ export const hasNotificationType = ( return !!(value & total); }; +export const getAdminPermission = (type: Notification): Permission => { + switch (type) { + case Notification.MEDIA_PENDING: + case Notification.MEDIA_APPROVED: + case Notification.MEDIA_AVAILABLE: + case Notification.MEDIA_FAILED: + case Notification.MEDIA_DECLINED: + case Notification.MEDIA_AUTO_APPROVED: + return Permission.MANAGE_REQUESTS; + case Notification.ISSUE_CREATED: + case Notification.ISSUE_COMMENT: + case Notification.ISSUE_RESOLVED: + case Notification.ISSUE_REOPENED: + return Permission.MANAGE_ISSUES; + default: + return Permission.ADMIN; + } +}; + +export const shouldSendAdminNotification = ( + type: Notification, + user: User, + payload: NotificationPayload +): boolean => { + return ( + user.id !== payload.notifyUser?.id && + user.hasPermission(getAdminPermission(type)) && + // Check if the user submitted this request (on behalf of themself OR another user) + (type !== Notification.MEDIA_AUTO_APPROVED || + user.id !== + (payload.request?.modifiedBy ?? payload.request?.requestedBy)?.id) && + // Check if the user created this issue + (type !== Notification.ISSUE_CREATED || + user.id !== payload.issue?.createdBy.id) && + // Check if the user submitted this issue comment + (type !== Notification.ISSUE_COMMENT || + user.id !== payload.comment?.user.id) && + // Check if the user resolved/reopened this issue + ((type !== Notification.ISSUE_RESOLVED && + type !== Notification.ISSUE_REOPENED) || + user.id !== payload.issue?.modifiedBy?.id) + ); +}; + class NotificationManager { private activeAgents: NotificationAgent[] = []; diff --git a/server/lib/permissions.ts b/server/lib/permissions.ts index fbf36e6b8..95160d380 100644 --- a/server/lib/permissions.ts +++ b/server/lib/permissions.ts @@ -19,6 +19,9 @@ export enum Permission { AUTO_APPROVE_4K_TV = 131072, REQUEST_MOVIE = 262144, REQUEST_TV = 524288, + MANAGE_ISSUES = 1048576, + VIEW_ISSUES = 2097152, + CREATE_ISSUES = 4194304, } export interface PermissionCheckOptions { diff --git a/server/lib/settings.ts b/server/lib/settings.ts index b4729e585..f7780dfc5 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -350,7 +350,7 @@ class Settings { options: { webhookUrl: '', jsonPayload: - 'IntcbiAgICBcIm5vdGlmaWNhdGlvbl90eXBlXCI6IFwie3tub3RpZmljYXRpb25fdHlwZX19XCIsXG4gICAgXCJzdWJqZWN0XCI6IFwie3tzdWJqZWN0fX1cIixcbiAgICBcIm1lc3NhZ2VcIjogXCJ7e21lc3NhZ2V9fVwiLFxuICAgIFwiaW1hZ2VcIjogXCJ7e2ltYWdlfX1cIixcbiAgICBcImVtYWlsXCI6IFwie3tub3RpZnl1c2VyX2VtYWlsfX1cIixcbiAgICBcInVzZXJuYW1lXCI6IFwie3tub3RpZnl1c2VyX3VzZXJuYW1lfX1cIixcbiAgICBcImF2YXRhclwiOiBcInt7bm90aWZ5dXNlcl9hdmF0YXJ9fVwiLFxuICAgIFwie3ttZWRpYX19XCI6IHtcbiAgICAgICAgXCJtZWRpYV90eXBlXCI6IFwie3ttZWRpYV90eXBlfX1cIixcbiAgICAgICAgXCJ0bWRiSWRcIjogXCJ7e21lZGlhX3RtZGJpZH19XCIsXG4gICAgICAgIFwiaW1kYklkXCI6IFwie3ttZWRpYV9pbWRiaWR9fVwiLFxuICAgICAgICBcInR2ZGJJZFwiOiBcInt7bWVkaWFfdHZkYmlkfX1cIixcbiAgICAgICAgXCJzdGF0dXNcIjogXCJ7e21lZGlhX3N0YXR1c319XCIsXG4gICAgICAgIFwic3RhdHVzNGtcIjogXCJ7e21lZGlhX3N0YXR1czRrfX1cIlxuICAgIH0sXG4gICAgXCJ7e2V4dHJhfX1cIjogW10sXG4gICAgXCJ7e3JlcXVlc3R9fVwiOiB7XG4gICAgICAgIFwicmVxdWVzdF9pZFwiOiBcInt7cmVxdWVzdF9pZH19XCIsXG4gICAgICAgIFwicmVxdWVzdGVkQnlfZW1haWxcIjogXCJ7e3JlcXVlc3RlZEJ5X2VtYWlsfX1cIixcbiAgICAgICAgXCJyZXF1ZXN0ZWRCeV91c2VybmFtZVwiOiBcInt7cmVxdWVzdGVkQnlfdXNlcm5hbWV9fVwiLFxuICAgICAgICBcInJlcXVlc3RlZEJ5X2F2YXRhclwiOiBcInt7cmVxdWVzdGVkQnlfYXZhdGFyfX1cIlxuICAgIH1cbn0i', + 'IntcbiAgICBcIm5vdGlmaWNhdGlvbl90eXBlXCI6IFwie3tub3RpZmljYXRpb25fdHlwZX19XCIsXG4gICAgXCJldmVudFwiOiBcInt7ZXZlbnR9fVwiLFxuICAgIFwic3ViamVjdFwiOiBcInt7c3ViamVjdH19XCIsXG4gICAgXCJtZXNzYWdlXCI6IFwie3ttZXNzYWdlfX1cIixcbiAgICBcImltYWdlXCI6IFwie3tpbWFnZX19XCIsXG4gICAgXCJ7e21lZGlhfX1cIjoge1xuICAgICAgICBcIm1lZGlhX3R5cGVcIjogXCJ7e21lZGlhX3R5cGV9fVwiLFxuICAgICAgICBcInRtZGJJZFwiOiBcInt7bWVkaWFfdG1kYmlkfX1cIixcbiAgICAgICAgXCJ0dmRiSWRcIjogXCJ7e21lZGlhX3R2ZGJpZH19XCIsXG4gICAgICAgIFwic3RhdHVzXCI6IFwie3ttZWRpYV9zdGF0dXN9fVwiLFxuICAgICAgICBcInN0YXR1czRrXCI6IFwie3ttZWRpYV9zdGF0dXM0a319XCJcbiAgICB9LFxuICAgIFwie3tyZXF1ZXN0fX1cIjoge1xuICAgICAgICBcInJlcXVlc3RfaWRcIjogXCJ7e3JlcXVlc3RfaWR9fVwiLFxuICAgICAgICBcInJlcXVlc3RlZEJ5X2VtYWlsXCI6IFwie3tyZXF1ZXN0ZWRCeV9lbWFpbH19XCIsXG4gICAgICAgIFwicmVxdWVzdGVkQnlfdXNlcm5hbWVcIjogXCJ7e3JlcXVlc3RlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICAgICAgXCJyZXF1ZXN0ZWRCeV9hdmF0YXJcIjogXCJ7e3JlcXVlc3RlZEJ5X2F2YXRhcn19XCJcbiAgICB9LFxuICAgIFwie3tpc3N1ZX19XCI6IHtcbiAgICAgICAgXCJpc3N1ZV9pZFwiOiBcInt7aXNzdWVfaWR9fVwiLFxuICAgICAgICBcImlzc3VlX3R5cGVcIjogXCJ7e2lzc3VlX3R5cGV9fVwiLFxuICAgICAgICBcImlzc3VlX3N0YXR1c1wiOiBcInt7aXNzdWVfc3RhdHVzfX1cIixcbiAgICAgICAgXCJyZXBvcnRlZEJ5X2VtYWlsXCI6IFwie3tyZXBvcnRlZEJ5X2VtYWlsfX1cIixcbiAgICAgICAgXCJyZXBvcnRlZEJ5X3VzZXJuYW1lXCI6IFwie3tyZXBvcnRlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICAgICAgXCJyZXBvcnRlZEJ5X2F2YXRhclwiOiBcInt7cmVwb3J0ZWRCeV9hdmF0YXJ9fVwiXG4gICAgfSxcbiAgICBcInt7Y29tbWVudH19XCI6IHtcbiAgICAgICAgXCJjb21tZW50X21lc3NhZ2VcIjogXCJ7e2NvbW1lbnRfbWVzc2FnZX19XCIsXG4gICAgICAgIFwiY29tbWVudGVkQnlfZW1haWxcIjogXCJ7e2NvbW1lbnRlZEJ5X2VtYWlsfX1cIixcbiAgICAgICAgXCJjb21tZW50ZWRCeV91c2VybmFtZVwiOiBcInt7Y29tbWVudGVkQnlfdXNlcm5hbWV9fVwiLFxuICAgICAgICBcImNvbW1lbnRlZEJ5X2F2YXRhclwiOiBcInt7Y29tbWVudGVkQnlfYXZhdGFyfX1cIlxuICAgIH0sXG4gICAgXCJ7e2V4dHJhfX1cIjogW11cbn0i', }, }, webpush: { diff --git a/server/migration/1634904083966-AddIssues.ts b/server/migration/1634904083966-AddIssues.ts new file mode 100644 index 000000000..0c6116f9d --- /dev/null +++ b/server/migration/1634904083966-AddIssues.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIssues1634904083966 implements MigrationInterface { + name = 'AddIssues1634904083966'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "issue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "issueType" integer NOT NULL, "status" integer NOT NULL DEFAULT (1), "problemSeason" integer NOT NULL DEFAULT (0), "problemEpisode" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "mediaId" integer, "createdById" integer, "modifiedById" integer)` + ); + await queryRunner.query( + `CREATE TABLE "issue_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message" text NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "issueId" integer)` + ); + await queryRunner.query( + `CREATE TABLE "temporary_issue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "issueType" integer NOT NULL, "status" integer NOT NULL DEFAULT (1), "problemSeason" integer NOT NULL DEFAULT (0), "problemEpisode" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "mediaId" integer, "createdById" integer, "modifiedById" integer, CONSTRAINT "FK_276e20d053f3cff1645803c95d8" FOREIGN KEY ("mediaId") REFERENCES "media" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_10b17b49d1ee77e7184216001e0" FOREIGN KEY ("createdById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_da88a1019c850d1a7b143ca02e5" FOREIGN KEY ("modifiedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_issue"("id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById") SELECT "id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById" FROM "issue"` + ); + await queryRunner.query(`DROP TABLE "issue"`); + await queryRunner.query(`ALTER TABLE "temporary_issue" RENAME TO "issue"`); + await queryRunner.query( + `CREATE TABLE "temporary_issue_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message" text NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "issueId" integer, CONSTRAINT "FK_707b033c2d0653f75213614789d" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_180710fead1c94ca499c57a7d42" FOREIGN KEY ("issueId") REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_issue_comment"("id", "message", "createdAt", "updatedAt", "userId", "issueId") SELECT "id", "message", "createdAt", "updatedAt", "userId", "issueId" FROM "issue_comment"` + ); + await queryRunner.query(`DROP TABLE "issue_comment"`); + await queryRunner.query( + `ALTER TABLE "temporary_issue_comment" RENAME TO "issue_comment"` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "issue_comment" RENAME TO "temporary_issue_comment"` + ); + await queryRunner.query( + `CREATE TABLE "issue_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message" text NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "issueId" integer)` + ); + await queryRunner.query( + `INSERT INTO "issue_comment"("id", "message", "createdAt", "updatedAt", "userId", "issueId") SELECT "id", "message", "createdAt", "updatedAt", "userId", "issueId" FROM "temporary_issue_comment"` + ); + await queryRunner.query(`DROP TABLE "temporary_issue_comment"`); + await queryRunner.query(`ALTER TABLE "issue" RENAME TO "temporary_issue"`); + await queryRunner.query( + `CREATE TABLE "issue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "issueType" integer NOT NULL, "status" integer NOT NULL DEFAULT (1), "problemSeason" integer NOT NULL DEFAULT (0), "problemEpisode" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "mediaId" integer, "createdById" integer, "modifiedById" integer)` + ); + await queryRunner.query( + `INSERT INTO "issue"("id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById") SELECT "id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById" FROM "temporary_issue"` + ); + await queryRunner.query(`DROP TABLE "temporary_issue"`); + await queryRunner.query(`DROP TABLE "issue_comment"`); + await queryRunner.query(`DROP TABLE "issue"`); + } +} diff --git a/server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts b/server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts new file mode 100644 index 000000000..8934866fa --- /dev/null +++ b/server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPushbulletPushoverUserSettings1635079863457 + implements MigrationInterface +{ + name = 'AddPushbulletPushoverUserSettings1635079863457'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale" FROM "user_settings"` + ); + await queryRunner.query(`DROP TABLE "user_settings"`); + await queryRunner.query( + `ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"` + ); + await queryRunner.query( + `CREATE TABLE "user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale" FROM "temporary_user_settings"` + ); + await queryRunner.query(`DROP TABLE "temporary_user_settings"`); + } +} diff --git a/server/models/Collection.ts b/server/models/Collection.ts index 48112849b..9cc4f3788 100644 --- a/server/models/Collection.ts +++ b/server/models/Collection.ts @@ -1,3 +1,4 @@ +import { sortBy } from 'lodash'; import type { TmdbCollection } from '../api/themoviedb/interfaces'; import { MediaType } from '../constants/media'; import Media from '../entity/Media'; @@ -21,7 +22,7 @@ export const mapCollection = ( overview: collection.overview, posterPath: collection.poster_path, backdropPath: collection.backdrop_path, - parts: collection.parts.map((part) => + parts: sortBy(collection.parts, 'release_date').map((part) => mapMovieResult( part, media?.find( diff --git a/server/models/Tv.ts b/server/models/Tv.ts index 9f6b25687..b596b1d2b 100644 --- a/server/models/Tv.ts +++ b/server/models/Tv.ts @@ -90,6 +90,10 @@ export interface TvDetails { overview: string; popularity: number; productionCompanies: ProductionCompany[]; + productionCountries: { + iso_3166_1: string; + name: string; + }[]; spokenLanguages: SpokenLanguage[]; seasons: Season[]; status: string; @@ -187,6 +191,7 @@ export const mapTvDetails = ( originCountry: company.origin_country, logoPath: company.logo_path, })), + productionCountries: show.production_countries, contentRatings: show.content_ratings, spokenLanguages: show.spoken_languages.map((language) => ({ englishName: language.english_name, diff --git a/server/routes/index.ts b/server/routes/index.ts index 25386e05b..3f57e8154 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -14,6 +14,8 @@ import { isPerson } from '../utils/typeHelpers'; import authRoutes from './auth'; import collectionRoutes from './collection'; import discoverRoutes, { createTmdbWithRegionLanguage } from './discover'; +import issueRoutes from './issue'; +import issueCommentRoutes from './issueComment'; import mediaRoutes from './media'; import movieRoutes from './movie'; import personRoutes from './person'; @@ -108,6 +110,8 @@ router.use('/media', isAuthenticated(), mediaRoutes); router.use('/person', isAuthenticated(), personRoutes); router.use('/collection', isAuthenticated(), collectionRoutes); router.use('/service', isAuthenticated(), serviceRoutes); +router.use('/issue', isAuthenticated(), issueRoutes); +router.use('/issueComment', isAuthenticated(), issueCommentRoutes); router.use('/auth', authRoutes); router.get('/regions', isAuthenticated(), async (req, res) => { diff --git a/server/routes/issue.ts b/server/routes/issue.ts new file mode 100644 index 000000000..c7db5232c --- /dev/null +++ b/server/routes/issue.ts @@ -0,0 +1,332 @@ +import { Router } from 'express'; +import { getRepository } from 'typeorm'; +import { IssueStatus } from '../constants/issue'; +import Issue from '../entity/Issue'; +import IssueComment from '../entity/IssueComment'; +import Media from '../entity/Media'; +import { IssueResultsResponse } from '../interfaces/api/issueInterfaces'; +import { Permission } from '../lib/permissions'; +import logger from '../logger'; +import { isAuthenticated } from '../middleware/auth'; + +const issueRoutes = Router(); + +issueRoutes.get, IssueResultsResponse>( + '/', + isAuthenticated( + [ + Permission.MANAGE_ISSUES, + Permission.VIEW_ISSUES, + Permission.CREATE_ISSUES, + ], + { type: 'or' } + ), + async (req, res, next) => { + const pageSize = req.query.take ? Number(req.query.take) : 10; + const skip = req.query.skip ? Number(req.query.skip) : 0; + const createdBy = req.query.createdBy ? Number(req.query.createdBy) : null; + + let sortFilter: string; + + switch (req.query.sort) { + case 'modified': + sortFilter = 'issue.updatedAt'; + break; + default: + sortFilter = 'issue.createdAt'; + } + + let statusFilter: IssueStatus[]; + + switch (req.query.filter) { + case 'open': + statusFilter = [IssueStatus.OPEN]; + break; + case 'resolved': + statusFilter = [IssueStatus.RESOLVED]; + break; + default: + statusFilter = [IssueStatus.OPEN, IssueStatus.RESOLVED]; + } + + let query = getRepository(Issue) + .createQueryBuilder('issue') + .leftJoinAndSelect('issue.createdBy', 'createdBy') + .leftJoinAndSelect('issue.media', 'media') + .leftJoinAndSelect('issue.modifiedBy', 'modifiedBy') + .where('issue.status IN (:...issueStatus)', { + issueStatus: statusFilter, + }); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], + { type: 'or' } + ) + ) { + if (createdBy && createdBy !== req.user?.id) { + return next({ + status: 403, + message: + 'You do not have permission to view issues reported by other users', + }); + } + query = query.andWhere('createdBy.id = :id', { id: req.user?.id }); + } else if (createdBy) { + query = query.andWhere('createdBy.id = :id', { id: createdBy }); + } + + const [issues, issueCount] = await query + .orderBy(sortFilter, 'DESC') + .take(pageSize) + .skip(skip) + .getManyAndCount(); + + return res.status(200).json({ + pageInfo: { + pages: Math.ceil(issueCount / pageSize), + pageSize, + results: issueCount, + page: Math.ceil(skip / pageSize) + 1, + }, + results: issues, + }); + } +); + +issueRoutes.post< + Record, + Issue, + { + message: string; + mediaId: number; + issueType: number; + problemSeason: number; + problemEpisode: number; + } +>( + '/', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + const issueRepository = getRepository(Issue); + const mediaRepository = getRepository(Media); + + const media = await mediaRepository.findOne({ + where: { id: req.body.mediaId }, + }); + + if (!media) { + return next({ status: 404, message: 'Media does not exist.' }); + } + + const issue = new Issue({ + createdBy: req.user, + issueType: req.body.issueType, + problemSeason: req.body.problemSeason, + problemEpisode: req.body.problemEpisode, + media, + comments: [ + new IssueComment({ + user: req.user, + message: req.body.message, + }), + ], + }); + + const newIssue = await issueRepository.save(issue); + + return res.status(200).json(newIssue); + } +); + +issueRoutes.get<{ issueId: string }>( + '/:issueId', + isAuthenticated( + [ + Permission.MANAGE_ISSUES, + Permission.VIEW_ISSUES, + Permission.CREATE_ISSUES, + ], + { type: 'or' } + ), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + try { + const issue = await issueRepository + .createQueryBuilder('issue') + .leftJoinAndSelect('issue.comments', 'comments') + .leftJoinAndSelect('issue.createdBy', 'createdBy') + .leftJoinAndSelect('comments.user', 'user') + .leftJoinAndSelect('issue.media', 'media') + .where('issue.id = :issueId', { issueId: Number(req.params.issueId) }) + .getOneOrFail(); + + if ( + issue.createdBy.id !== req.user.id && + !req.user.hasPermission( + [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], + { type: 'or' } + ) + ) { + return next({ + status: 403, + message: 'You do not have permission to view this issue.', + }); + } + + return res.status(200).json(issue); + } catch (e) { + logger.debug('Failed to retrieve issue.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 500, message: 'Issue not found.' }); + } + } +); + +issueRoutes.post<{ issueId: string }, Issue, { message: string }>( + '/:issueId/comment', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + try { + const issue = await issueRepository.findOneOrFail({ + where: { id: Number(req.params.issueId) }, + }); + + if ( + issue.createdBy.id !== req.user.id && + !req.user.hasPermission(Permission.MANAGE_ISSUES) + ) { + return next({ + status: 403, + message: 'You do not have permission to comment on this issue.', + }); + } + + const comment = new IssueComment({ + message: req.body.message, + user: req.user, + }); + + issue.comments = [...issue.comments, comment]; + + await issueRepository.save(issue); + + return res.status(200).json(issue); + } catch (e) { + logger.debug('Something went wrong creating an issue comment.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 500, message: 'Issue not found.' }); + } + } +); + +issueRoutes.post<{ issueId: string; status: string }, Issue>( + '/:issueId/:status', + isAuthenticated(Permission.MANAGE_ISSUES), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + try { + const issue = await issueRepository.findOneOrFail({ + where: { id: Number(req.params.issueId) }, + }); + + let newStatus: IssueStatus | undefined; + + switch (req.params.status) { + case 'resolved': + newStatus = IssueStatus.RESOLVED; + break; + case 'open': + newStatus = IssueStatus.OPEN; + } + + if (!newStatus) { + return next({ + status: 400, + message: 'You must provide a valid status', + }); + } + + issue.status = newStatus; + issue.modifiedBy = req.user; + + await issueRepository.save(issue); + + return res.status(200).json(issue); + } catch (e) { + logger.debug('Something went wrong creating an issue comment.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 500, message: 'Issue not found.' }); + } + } +); + +issueRoutes.delete( + '/:issueId', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + + try { + const issue = await issueRepository.findOneOrFail({ + where: { id: Number(req.params.issueId) }, + relations: ['createdBy'], + }); + + if ( + !req.user?.hasPermission(Permission.MANAGE_ISSUES) && + (issue.createdBy.id !== req.user?.id || issue.comments.length > 1) + ) { + return next({ + status: 401, + message: 'You do not have permission to delete this issue.', + }); + } + + await issueRepository.remove(issue); + + return res.status(204).send(); + } catch (e) { + logger.error('Something went wrong deleting an issue.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue not found.' }); + } + } +); + +export default issueRoutes; diff --git a/server/routes/issueComment.ts b/server/routes/issueComment.ts new file mode 100644 index 000000000..c54bce5b6 --- /dev/null +++ b/server/routes/issueComment.ts @@ -0,0 +1,129 @@ +import { Router } from 'express'; +import { getRepository } from 'typeorm'; +import IssueComment from '../entity/IssueComment'; +import { Permission } from '../lib/permissions'; +import logger from '../logger'; +import { isAuthenticated } from '../middleware/auth'; + +const issueCommentRoutes = Router(); + +issueCommentRoutes.get<{ commentId: string }, IssueComment>( + '/:commentId', + isAuthenticated( + [ + Permission.MANAGE_ISSUES, + Permission.VIEW_ISSUES, + Permission.CREATE_ISSUES, + ], + { + type: 'or', + } + ), + async (req, res, next) => { + const issueCommentRepository = getRepository(IssueComment); + + try { + const comment = await issueCommentRepository.findOneOrFail({ + where: { id: Number(req.params.commentId) }, + }); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], + { type: 'or' } + ) && + comment.user.id !== req.user?.id + ) { + return next({ + status: 403, + message: 'You do not have permission to view this comment.', + }); + } + + return res.status(200).json(comment); + } catch (e) { + logger.debug('Request for unknown issue comment failed', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue comment not found.' }); + } + } +); + +issueCommentRoutes.put< + { commentId: string }, + IssueComment, + { message: string } +>( + '/:commentId', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueCommentRepository = getRepository(IssueComment); + + try { + const comment = await issueCommentRepository.findOneOrFail({ + where: { id: Number(req.params.commentId) }, + }); + + if (comment.user.id !== req.user?.id) { + return next({ + status: 403, + message: 'You can only edit your own comments.', + }); + } + + comment.message = req.body.message; + + await issueCommentRepository.save(comment); + + return res.status(200).json(comment); + } catch (e) { + logger.debug('Put request for issue comment failed', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue comment not found.' }); + } + } +); + +issueCommentRoutes.delete<{ commentId: string }, IssueComment>( + '/:commentId', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueCommentRepository = getRepository(IssueComment); + + try { + const comment = await issueCommentRepository.findOneOrFail({ + where: { id: Number(req.params.commentId) }, + }); + + if ( + !req.user?.hasPermission([Permission.MANAGE_ISSUES], { type: 'or' }) && + comment.user.id !== req.user?.id + ) { + return next({ + status: 403, + message: 'You do not have permission to delete this comment.', + }); + } + + await issueCommentRepository.remove(comment); + + return res.status(204).send(); + } catch (e) { + logger.debug('Delete request for issue comment failed', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue comment not found.' }); + } + } +); + +export default issueCommentRoutes; diff --git a/server/routes/request.ts b/server/routes/request.ts index 8fed74107..2e79ff4a4 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -13,131 +13,134 @@ import { isAuthenticated } from '../middleware/auth'; const requestRoutes = Router(); -requestRoutes.get('/', async (req, res, next) => { - try { - const pageSize = req.query.take ? Number(req.query.take) : 10; - const skip = req.query.skip ? Number(req.query.skip) : 0; - const requestedBy = req.query.requestedBy - ? Number(req.query.requestedBy) - : null; +requestRoutes.get, RequestResultsResponse>( + '/', + async (req, res, next) => { + try { + const pageSize = req.query.take ? Number(req.query.take) : 10; + const skip = req.query.skip ? Number(req.query.skip) : 0; + const requestedBy = req.query.requestedBy + ? Number(req.query.requestedBy) + : null; - let statusFilter: MediaRequestStatus[]; + let statusFilter: MediaRequestStatus[]; - switch (req.query.filter) { - case 'approved': - case 'processing': - case 'available': - statusFilter = [MediaRequestStatus.APPROVED]; - break; - case 'pending': - statusFilter = [MediaRequestStatus.PENDING]; - break; - case 'unavailable': - statusFilter = [ - MediaRequestStatus.PENDING, - MediaRequestStatus.APPROVED, - ]; - break; - default: - statusFilter = [ - MediaRequestStatus.PENDING, - MediaRequestStatus.APPROVED, - MediaRequestStatus.DECLINED, - ]; - } + switch (req.query.filter) { + case 'approved': + case 'processing': + case 'available': + statusFilter = [MediaRequestStatus.APPROVED]; + break; + case 'pending': + statusFilter = [MediaRequestStatus.PENDING]; + break; + case 'unavailable': + statusFilter = [ + MediaRequestStatus.PENDING, + MediaRequestStatus.APPROVED, + ]; + break; + default: + statusFilter = [ + MediaRequestStatus.PENDING, + MediaRequestStatus.APPROVED, + MediaRequestStatus.DECLINED, + ]; + } - let mediaStatusFilter: MediaStatus[]; + let mediaStatusFilter: MediaStatus[]; - switch (req.query.filter) { - case 'available': - mediaStatusFilter = [MediaStatus.AVAILABLE]; - break; - case 'processing': - case 'unavailable': - mediaStatusFilter = [ - MediaStatus.UNKNOWN, - MediaStatus.PENDING, - MediaStatus.PROCESSING, - MediaStatus.PARTIALLY_AVAILABLE, - ]; - break; - default: - mediaStatusFilter = [ - MediaStatus.UNKNOWN, - MediaStatus.PENDING, - MediaStatus.PROCESSING, - MediaStatus.PARTIALLY_AVAILABLE, - MediaStatus.AVAILABLE, - ]; - } + switch (req.query.filter) { + case 'available': + mediaStatusFilter = [MediaStatus.AVAILABLE]; + break; + case 'processing': + case 'unavailable': + mediaStatusFilter = [ + MediaStatus.UNKNOWN, + MediaStatus.PENDING, + MediaStatus.PROCESSING, + MediaStatus.PARTIALLY_AVAILABLE, + ]; + break; + default: + mediaStatusFilter = [ + MediaStatus.UNKNOWN, + MediaStatus.PENDING, + MediaStatus.PROCESSING, + MediaStatus.PARTIALLY_AVAILABLE, + MediaStatus.AVAILABLE, + ]; + } - let sortFilter: string; + let sortFilter: string; - switch (req.query.sort) { - case 'modified': - sortFilter = 'request.updatedAt'; - break; - default: - sortFilter = 'request.id'; - } + switch (req.query.sort) { + case 'modified': + sortFilter = 'request.updatedAt'; + break; + default: + sortFilter = 'request.id'; + } - let query = getRepository(MediaRequest) - .createQueryBuilder('request') - .leftJoinAndSelect('request.media', 'media') - .leftJoinAndSelect('request.seasons', 'seasons') - .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') - .leftJoinAndSelect('request.requestedBy', 'requestedBy') - .where('request.status IN (:...requestStatus)', { - requestStatus: statusFilter, - }) - .andWhere( - '((request.is4k = 0 AND media.status IN (:...mediaStatus)) OR (request.is4k = 1 AND media.status4k IN (:...mediaStatus)))', - { - mediaStatus: mediaStatusFilter, + let query = getRepository(MediaRequest) + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .leftJoinAndSelect('request.seasons', 'seasons') + .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') + .leftJoinAndSelect('request.requestedBy', 'requestedBy') + .where('request.status IN (:...requestStatus)', { + requestStatus: statusFilter, + }) + .andWhere( + '((request.is4k = 0 AND media.status IN (:...mediaStatus)) OR (request.is4k = 1 AND media.status4k IN (:...mediaStatus)))', + { + mediaStatus: mediaStatusFilter, + } + ); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], + { type: 'or' } + ) + ) { + if (requestedBy && requestedBy !== req.user?.id) { + return next({ + status: 403, + message: "You do not have permission to view this user's requests.", + }); } - ); - if ( - !req.user?.hasPermission( - [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], - { type: 'or' } - ) - ) { - if (requestedBy && requestedBy !== req.user?.id) { - return next({ - status: 403, - message: "You do not have permission to view this user's requests.", + query = query.andWhere('requestedBy.id = :id', { + id: req.user?.id, + }); + } else if (requestedBy) { + query = query.andWhere('requestedBy.id = :id', { + id: requestedBy, }); } - query = query.andWhere('requestedBy.id = :id', { - id: req.user?.id, - }); - } else if (requestedBy) { - query = query.andWhere('requestedBy.id = :id', { - id: requestedBy, + const [requests, requestCount] = await query + .orderBy(sortFilter, 'DESC') + .take(pageSize) + .skip(skip) + .getManyAndCount(); + + return res.status(200).json({ + pageInfo: { + pages: Math.ceil(requestCount / pageSize), + pageSize, + results: requestCount, + page: Math.ceil(skip / pageSize) + 1, + }, + results: requests, }); + } catch (e) { + next({ status: 500, message: e.message }); } - - const [requests, requestCount] = await query - .orderBy(sortFilter, 'DESC') - .take(pageSize) - .skip(skip) - .getManyAndCount(); - - return res.status(200).json({ - pageInfo: { - pages: Math.ceil(requestCount / pageSize), - pageSize, - results: requestCount, - page: Math.ceil(skip / pageSize) + 1, - }, - results: requests, - } as RequestResultsResponse); - } catch (e) { - next({ status: 500, message: e.message }); } -}); +); requestRoutes.post('/', async (req, res, next) => { const tmdb = new TheMovieDb(); @@ -497,9 +500,26 @@ requestRoutes.get('/:requestId', async (req, res, next) => { relations: ['requestedBy', 'modifiedBy'], }); + if ( + request.requestedBy.id !== req.user?.id && + !req.user?.hasPermission( + [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], + { type: 'or' } + ) + ) { + return next({ + status: 403, + message: 'You do not have permission to view this request.', + }); + } + return res.status(200).json(request); } catch (e) { - next({ status: 404, message: 'Request not found' }); + logger.debug('Failed to retrieve request.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Request not found.' }); } }); @@ -529,11 +549,11 @@ requestRoutes.put<{ requestId: string }>( }); } - let requestUser = req.user; + let requestUser = request.requestedBy; if ( req.body.userId && - req.body.userId !== req.user?.id && + req.body.userId !== request.requestedBy.id && !req.user?.hasPermission([ Permission.MANAGE_USERS, Permission.MANAGE_REQUESTS, @@ -665,7 +685,10 @@ requestRoutes.delete('/:requestId', async (req, res, next) => { return res.status(204).send(); } catch (e) { - logger.error(e.message); + logger.error('Something went wrong deleting a request.', { + label: 'API', + errorMessage: e.message, + }); next({ status: 404, message: 'Request not found.' }); } }); diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index f58edb748..bad91eaca 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -258,25 +258,29 @@ settingsRoutes.get( try { fs.readFileSync(logFile) .toString() - .split('\n') + .split(/(?=\n\d{4}-\d{2})/g) .forEach((line) => { if (!line.length) return; - const timestamp = line.match(new RegExp(/^.{24}/)) || []; - const level = line.match(new RegExp(/\s\[\w+\]/)) || []; - const label = line.match(new RegExp(/\]\[.+?\]/)) || []; - const message = line.match(new RegExp(/:\s([^{}]+)({.*})?/)) || []; + const jsonRegexp = new RegExp( + /[{[]{1}([,:{}[\]0-9.\-+Eaeflnr-u \n\r\t]|"[^"\n]*?")+[}\]]{1}/ + ); - if (level.length && filter.includes(level[0].slice(2, -1))) { + const timestamp = line.match(new RegExp(/.{24}/)) || []; + const level = line.match(new RegExp(/(?<=.{24}\s\[).+?(?=\])/)) || []; + const label = + line.match(new RegExp(/(?<=.{24}\s\[.+\]\[).+?(?=\])/)) || []; + const message = + line.match(new RegExp(/(?<=\[.+\]:\s)[\s\S][^\r]+/)) || []; + const data = message[0].match(jsonRegexp) || []; + + if (level.length && filter.includes(level[0])) { logs.push({ timestamp: timestamp[0], - level: level.length ? level[0].slice(2, -1) : '', - label: label.length ? label[0].slice(2, -1) : '', - message: message.length && message[1] ? message[1] : '', - data: - message.length && message[2] - ? JSON.parse(message[2]) - : undefined, + level: level[0], + label: label[0], + message: message[0].replace(jsonRegexp, ''), + data: data.length ? JSON.parse(data[0]) : undefined, }); } }); diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts index bb21c7b60..d98debb7a 100644 --- a/server/routes/settings/notifications.ts +++ b/server/routes/settings/notifications.ts @@ -1,5 +1,7 @@ import { Router } from 'express'; +import { User } from '../../entity/User'; import { Notification } from '../../lib/notifications'; +import { NotificationAgent } from '../../lib/notifications/agents/agent'; import DiscordAgent from '../../lib/notifications/agents/discord'; import EmailAgent from '../../lib/notifications/agents/email'; import LunaSeaAgent from '../../lib/notifications/agents/lunasea'; @@ -13,6 +15,14 @@ import { getSettings } from '../../lib/settings'; const notificationRoutes = Router(); +const sendTestNotification = async (agent: NotificationAgent, user: User) => + await agent.send(Notification.TEST_NOTIFICATION, { + notifyAdmin: false, + notifyUser: user, + subject: 'Test Notification', + message: 'Check check, 1, 2, 3. Are we coming in clear?', + }); + notificationRoutes.get('/discord', (_req, res) => { const settings = getSettings(); @@ -37,14 +47,7 @@ notificationRoutes.post('/discord/test', async (req, res, next) => { } const discordAgent = new DiscordAgent(req.body); - if ( - await discordAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(discordAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -78,14 +81,7 @@ notificationRoutes.post('/slack/test', async (req, res, next) => { } const slackAgent = new SlackAgent(req.body); - if ( - await slackAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(slackAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -119,14 +115,7 @@ notificationRoutes.post('/telegram/test', async (req, res, next) => { } const telegramAgent = new TelegramAgent(req.body); - if ( - await telegramAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(telegramAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -160,14 +149,7 @@ notificationRoutes.post('/pushbullet/test', async (req, res, next) => { } const pushbulletAgent = new PushbulletAgent(req.body); - if ( - await pushbulletAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(pushbulletAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -201,14 +183,7 @@ notificationRoutes.post('/pushover/test', async (req, res, next) => { } const pushoverAgent = new PushoverAgent(req.body); - if ( - await pushoverAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(pushoverAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -242,14 +217,7 @@ notificationRoutes.post('/email/test', async (req, res, next) => { } const emailAgent = new EmailAgent(req.body); - if ( - await emailAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(emailAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -283,14 +251,7 @@ notificationRoutes.post('/webpush/test', async (req, res, next) => { } const webpushAgent = new WebPushAgent(req.body); - if ( - await webpushAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(webpushAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -369,14 +330,7 @@ notificationRoutes.post('/webhook/test', async (req, res, next) => { }; const webhookAgent = new WebhookAgent(testBody); - if ( - await webhookAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(webhookAgent, req.user)) { return res.status(204).send(); } else { return next({ @@ -413,14 +367,7 @@ notificationRoutes.post('/lunasea/test', async (req, res, next) => { } const lunaseaAgent = new LunaSeaAgent(req.body); - if ( - await lunaseaAgent.send(Notification.TEST_NOTIFICATION, { - notifyUser: req.user, - subject: 'Test Notification', - message: - 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', - }) - ) { + if (await sendTestNotification(lunaseaAgent, req.user)) { return res.status(204).send(); } else { return next({ diff --git a/server/routes/settings/radarr.ts b/server/routes/settings/radarr.ts index fd5791c5a..a33bfcdba 100644 --- a/server/routes/settings/radarr.ts +++ b/server/routes/settings/radarr.ts @@ -46,7 +46,10 @@ radarrRoutes.post< url: RadarrAPI.buildUrl(req.body, '/api/v3'), }); - const { urlBase } = await radarr.getSystemStatus(); + const urlBase = await radarr + .getSystemStatus() + .then((value) => value.urlBase) + .catch(() => req.body.baseUrl); const profiles = await radarr.getProfiles(); const folders = await radarr.getRootFolders(); const tags = await radarr.getTags(); @@ -58,10 +61,7 @@ radarrRoutes.post< path: folder.path, })), tags, - urlBase: - req.body.baseUrl && req.body.baseUrl !== '/' - ? req.body.baseUrl - : urlBase, + urlBase, }); } catch (e) { logger.error('Failed to test Radarr', { diff --git a/server/routes/settings/sonarr.ts b/server/routes/settings/sonarr.ts index 09baabd95..da5a5bb3f 100644 --- a/server/routes/settings/sonarr.ts +++ b/server/routes/settings/sonarr.ts @@ -42,7 +42,10 @@ sonarrRoutes.post('/test', async (req, res, next) => { url: SonarrAPI.buildUrl(req.body, '/api/v3'), }); - const { urlBase } = await sonarr.getSystemStatus(); + const urlBase = await sonarr + .getSystemStatus() + .then((value) => value.urlBase) + .catch(() => req.body.baseUrl); const profiles = await sonarr.getProfiles(); const folders = await sonarr.getRootFolders(); const languageProfiles = await sonarr.getLanguageProfiles(); @@ -56,10 +59,7 @@ sonarrRoutes.post('/test', async (req, res, next) => { })), languageProfiles, tags, - urlBase: - req.body.baseUrl && req.body.baseUrl !== '/' - ? req.body.baseUrl - : urlBase, + urlBase, }); } catch (e) { logger.error('Failed to test Sonarr', { diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 226dcae09..6558115a7 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -257,6 +257,9 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>( ? settings?.discord.types : 0, discordId: user.settings?.discordId, + pushbulletAccessToken: user.settings?.pushbulletAccessToken, + pushoverApplicationToken: user.settings?.pushoverApplicationToken, + pushoverUserKey: user.settings?.pushoverUserKey, telegramEnabled: settings?.telegram.enabled, telegramBotUsername: settings?.telegram.options.botUsername, telegramChatId: user.settings?.telegramChatId, @@ -298,6 +301,9 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( user: req.user, pgpKey: req.body.pgpKey, discordId: req.body.discordId, + pushbulletAccessToken: req.body.pushbulletAccessToken, + pushoverApplicationToken: req.body.pushoverApplicationToken, + pushoverUserKey: req.body.pushoverUserKey, telegramChatId: req.body.telegramChatId, telegramSendSilently: req.body.telegramSendSilently, notificationTypes: req.body.notificationTypes, @@ -305,6 +311,10 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( } else { user.settings.pgpKey = req.body.pgpKey; user.settings.discordId = req.body.discordId; + user.settings.pushbulletAccessToken = req.body.pushbulletAccessToken; + user.settings.pushoverApplicationToken = + req.body.pushoverApplicationToken; + user.settings.pushoverUserKey = req.body.pushoverUserKey; user.settings.telegramChatId = req.body.telegramChatId; user.settings.telegramSendSilently = req.body.telegramSendSilently; user.settings.notificationTypes = Object.assign( @@ -319,6 +329,9 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( return res.status(200).json({ pgpKey: user.settings?.pgpKey, discordId: user.settings?.discordId, + pushbulletAccessToken: user.settings?.pushbulletAccessToken, + pushoverApplicationToken: user.settings?.pushoverApplicationToken, + pushoverUserKey: user.settings?.pushoverUserKey, telegramChatId: user.settings?.telegramChatId, telegramSendSilently: user?.settings?.telegramSendSilently, notificationTypes: user.settings.notificationTypes, diff --git a/server/subscriber/IssueCommentSubscriber.ts b/server/subscriber/IssueCommentSubscriber.ts new file mode 100644 index 000000000..c844b614c --- /dev/null +++ b/server/subscriber/IssueCommentSubscriber.ts @@ -0,0 +1,95 @@ +import { sortBy } from 'lodash'; +import { + EntitySubscriberInterface, + EventSubscriber, + getRepository, + InsertEvent, +} from 'typeorm'; +import TheMovieDb from '../api/themoviedb'; +import { IssueType, IssueTypeName } from '../constants/issue'; +import { MediaType } from '../constants/media'; +import IssueComment from '../entity/IssueComment'; +import Media from '../entity/Media'; +import notificationManager, { Notification } from '../lib/notifications'; +import { Permission } from '../lib/permissions'; + +@EventSubscriber() +export class IssueCommentSubscriber + implements EntitySubscriberInterface +{ + public listenTo(): typeof IssueComment { + return IssueComment; + } + + private async sendIssueCommentNotification(entity: IssueComment) { + let title: string; + let image: string; + const tmdb = new TheMovieDb(); + + const issue = ( + await getRepository(IssueComment).findOne({ + where: { id: entity.id }, + relations: ['issue'], + }) + )?.issue; + if (!issue) { + return; + } + + const media = await getRepository(Media).findOne({ + where: { id: issue.media.id }, + }); + if (!media) { + return; + } + + if (media.mediaType === MediaType.MOVIE) { + const movie = await tmdb.getMovie({ movieId: media.tmdbId }); + + title = `${movie.title}${ + movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' + }`; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`; + } else { + const tvshow = await tmdb.getTvShow({ tvId: media.tmdbId }); + + title = `${tvshow.name}${ + tvshow.first_air_date ? ` (${tvshow.first_air_date.slice(0, 4)})` : '' + }`; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`; + } + + const [firstComment] = sortBy(issue.comments, 'id'); + + if (entity.id !== firstComment.id) { + // Send notifications to all issue managers + notificationManager.sendNotification(Notification.ISSUE_COMMENT, { + event: `New Comment on ${ + issue.issueType !== IssueType.OTHER + ? `${IssueTypeName[issue.issueType]} ` + : '' + }Issue`, + subject: title, + message: firstComment.message, + comment: entity, + issue, + media, + image, + notifyAdmin: true, + notifyUser: + !issue.createdBy.hasPermission(Permission.MANAGE_ISSUES) && + issue.createdBy.id !== entity.user.id + ? issue.createdBy + : undefined, + }); + } + } + + public afterInsert(event: InsertEvent): void { + if (!event.entity) { + return; + } + + this.sendIssueCommentNotification(event.entity); + } +} diff --git a/server/subscriber/IssueSubscriber.ts b/server/subscriber/IssueSubscriber.ts new file mode 100644 index 000000000..5cf2be59e --- /dev/null +++ b/server/subscriber/IssueSubscriber.ts @@ -0,0 +1,124 @@ +import { sortBy } from 'lodash'; +import { + EntitySubscriberInterface, + EventSubscriber, + InsertEvent, + UpdateEvent, +} from 'typeorm'; +import TheMovieDb from '../api/themoviedb'; +import { IssueStatus, IssueType, IssueTypeName } from '../constants/issue'; +import { MediaType } from '../constants/media'; +import Issue from '../entity/Issue'; +import notificationManager, { Notification } from '../lib/notifications'; +import { Permission } from '../lib/permissions'; + +@EventSubscriber() +export class IssueSubscriber implements EntitySubscriberInterface { + public listenTo(): typeof Issue { + return Issue; + } + + private async sendIssueNotification(entity: Issue, type: Notification) { + let title: string; + let image: string; + const tmdb = new TheMovieDb(); + if (entity.media.mediaType === MediaType.MOVIE) { + const movie = await tmdb.getMovie({ movieId: entity.media.tmdbId }); + + title = `${movie.title}${ + movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' + }`; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`; + } else { + const tvshow = await tmdb.getTvShow({ tvId: entity.media.tmdbId }); + + title = `${tvshow.name}${ + tvshow.first_air_date ? ` (${tvshow.first_air_date.slice(0, 4)})` : '' + }`; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`; + } + + const [firstComment] = sortBy(entity.comments, 'id'); + const extra: { name: string; value: string }[] = []; + + if (entity.media.mediaType === MediaType.TV && entity.problemSeason > 0) { + extra.push({ + name: 'Affected Season', + value: entity.problemSeason.toString(), + }); + + if (entity.problemEpisode > 0) { + extra.push({ + name: 'Affected Episode', + value: entity.problemEpisode.toString(), + }); + } + } + + notificationManager.sendNotification(type, { + event: + type === Notification.ISSUE_CREATED + ? `New ${ + entity.issueType !== IssueType.OTHER + ? `${IssueTypeName[entity.issueType]} ` + : '' + }Issue Reported` + : type === Notification.ISSUE_RESOLVED + ? `${ + entity.issueType !== IssueType.OTHER + ? `${IssueTypeName[entity.issueType]} ` + : '' + }Issue Resolved` + : `${ + entity.issueType !== IssueType.OTHER + ? `${IssueTypeName[entity.issueType]} ` + : '' + }Issue Reopened`, + subject: title, + message: firstComment.message, + issue: entity, + media: entity.media, + image, + extra, + notifyAdmin: true, + notifyUser: + !entity.createdBy.hasPermission(Permission.MANAGE_ISSUES) && + (type === Notification.ISSUE_RESOLVED || + type === Notification.ISSUE_REOPENED) + ? entity.createdBy + : undefined, + }); + } + + public afterInsert(event: InsertEvent): void { + if (!event.entity) { + return; + } + + this.sendIssueNotification(event.entity, Notification.ISSUE_CREATED); + } + + public beforeUpdate(event: UpdateEvent): void { + if (!event.entity) { + return; + } + + if ( + event.entity.status === IssueStatus.RESOLVED && + event.databaseEntity.status !== IssueStatus.RESOLVED + ) { + this.sendIssueNotification( + event.entity as Issue, + Notification.ISSUE_RESOLVED + ); + } else if ( + event.entity.status === IssueStatus.OPEN && + event.databaseEntity.status !== IssueStatus.OPEN + ) { + this.sendIssueNotification( + event.entity as Issue, + Notification.ISSUE_REOPENED + ); + } + } +} diff --git a/server/subscriber/MediaSubscriber.ts b/server/subscriber/MediaSubscriber.ts index fb9bf24c2..1e279377e 100644 --- a/server/subscriber/MediaSubscriber.ts +++ b/server/subscriber/MediaSubscriber.ts @@ -3,6 +3,7 @@ import { EntitySubscriberInterface, EventSubscriber, getRepository, + Not, UpdateEvent, } from 'typeorm'; import TheMovieDb from '../api/themoviedb'; @@ -13,16 +14,24 @@ import Season from '../entity/Season'; import notificationManager, { Notification } from '../lib/notifications'; @EventSubscriber() -export class MediaSubscriber implements EntitySubscriberInterface { - private async notifyAvailableMovie(entity: Media, dbEntity?: Media) { +export class MediaSubscriber implements EntitySubscriberInterface { + private async notifyAvailableMovie( + entity: Media, + dbEntity: Media, + is4k: boolean + ) { if ( - entity.status === MediaStatus.AVAILABLE && - dbEntity?.status !== MediaStatus.AVAILABLE + entity[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE && + dbEntity[is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE ) { if (entity.mediaType === MediaType.MOVIE) { const requestRepository = getRepository(MediaRequest); const relatedRequests = await requestRepository.find({ - where: { media: entity, is4k: false }, + where: { + media: entity, + is4k, + status: Not(MediaRequestStatus.DECLINED), + }, }); if (relatedRequests.length > 0) { @@ -31,6 +40,8 @@ export class MediaSubscriber implements EntitySubscriberInterface { relatedRequests.forEach((request) => { notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, { + event: `${is4k ? '4K ' : ''}Movie Request Now Available`, + notifyAdmin: false, notifyUser: request.requestedBy, subject: `${movie.title}${ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' @@ -42,7 +53,7 @@ export class MediaSubscriber implements EntitySubscriberInterface { }), media: entity, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, - request: request, + request, }); }); } @@ -50,15 +61,25 @@ export class MediaSubscriber implements EntitySubscriberInterface { } } - private async notifyAvailableSeries(entity: Media, dbEntity: Media) { + private async notifyAvailableSeries( + entity: Media, + dbEntity: Media, + is4k: boolean + ) { const seasonRepository = getRepository(Season); const newAvailableSeasons = entity.seasons - .filter((season) => season.status === MediaStatus.AVAILABLE) + .filter( + (season) => + season[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE + ) .map((season) => season.seasonNumber); const oldSeasonIds = dbEntity.seasons.map((season) => season.id); const oldSeasons = await seasonRepository.findByIds(oldSeasonIds); const oldAvailableSeasons = oldSeasons - .filter((season) => season.status === MediaStatus.AVAILABLE) + .filter( + (season) => + season[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE + ) .map((season) => season.seasonNumber); const changedSeasons = newAvailableSeasons.filter( @@ -72,7 +93,11 @@ export class MediaSubscriber implements EntitySubscriberInterface { for (const changedSeasonNumber of changedSeasons) { const requests = await requestRepository.find({ - where: { media: entity, is4k: false }, + where: { + media: entity, + is4k, + status: Not(MediaRequestStatus.DECLINED), + }, }); const request = requests.find( (request) => @@ -91,6 +116,7 @@ export class MediaSubscriber implements EntitySubscriberInterface { ); const tv = await tmdb.getTvShow({ tvId: entity.tmdbId }); notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, { + event: `${is4k ? '4K ' : ''}Series Request Now Available`, subject: `${tv.name}${ tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : '' }`, @@ -99,18 +125,19 @@ export class MediaSubscriber implements EntitySubscriberInterface { separator: /\s/, omission: '…', }), + notifyAdmin: false, notifyUser: request.requestedBy, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`, media: entity, extra: [ { - name: 'Seasons', + name: 'Requested Seasons', value: request.seasons .map((season) => season.seasonNumber) .join(', '), }, ], - request: request, + request, }); } } @@ -144,7 +171,22 @@ export class MediaSubscriber implements EntitySubscriberInterface { event.entity.mediaType === MediaType.MOVIE && event.entity.status === MediaStatus.AVAILABLE ) { - this.notifyAvailableMovie(event.entity as Media, event.databaseEntity); + this.notifyAvailableMovie( + event.entity as Media, + event.databaseEntity, + false + ); + } + + if ( + event.entity.mediaType === MediaType.MOVIE && + event.entity.status4k === MediaStatus.AVAILABLE + ) { + this.notifyAvailableMovie( + event.entity as Media, + event.databaseEntity, + true + ); } if ( @@ -152,7 +194,23 @@ export class MediaSubscriber implements EntitySubscriberInterface { (event.entity.status === MediaStatus.AVAILABLE || event.entity.status === MediaStatus.PARTIALLY_AVAILABLE) ) { - this.notifyAvailableSeries(event.entity as Media, event.databaseEntity); + this.notifyAvailableSeries( + event.entity as Media, + event.databaseEntity, + false + ); + } + + if ( + event.entity.mediaType === MediaType.TV && + (event.entity.status4k === MediaStatus.AVAILABLE || + event.entity.status4k === MediaStatus.PARTIALLY_AVAILABLE) + ) { + this.notifyAvailableSeries( + event.entity as Media, + event.databaseEntity, + true + ); } if ( @@ -169,4 +227,8 @@ export class MediaSubscriber implements EntitySubscriberInterface { this.updateChildRequestStatus(event.entity as Media, true); } } + + public listenTo(): typeof Media { + return Media; + } } diff --git a/server/templates/email/media-issue/html.pug b/server/templates/email/media-issue/html.pug new file mode 100644 index 000000000..920542e0b --- /dev/null +++ b/server/templates/email/media-issue/html.pug @@ -0,0 +1,53 @@ +doctype html +head + meta(charset='utf-8') + meta(name='x-apple-disable-message-reformatting') + meta(http-equiv='x-ua-compatible' content='ie=edge') + meta(name='viewport' content='width=device-width, initial-scale=1') + meta(name='format-detection' content='telephone=no, date=no, address=no, email=no') + link(href='https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap' rel='stylesheet' media='screen') + style. + .title:hover * { + text-decoration: underline; + } + @media only screen and (max-width:600px) { + table { + font-size: 20px !important; + width: 100% !important; + } + } +div(style='display: block; background-color: #111827; padding: 2.5rem 0;') + table(style='margin: 0 auto; font-family: Inter, Arial, sans-serif; color: #fff; font-size: 16px; width: 26rem;') + tr + td(style="text-align: center;") + if applicationUrl + a(href=applicationUrl style='margin: 0 1rem;') + img(src=applicationUrl +'/logo_full.png' style='width: 26rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + else + div(style='margin: 0 1rem 2.5rem; font-size: 3em; font-weight: 700;') + | #{applicationTitle} + if recipientName !== recipientEmail + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | Hi, #{recipientName.replace(/\.|@/g, ((x) => x + '\ufeff'))}! + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | #{body} + if issueComment + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | #{issueComment} + else if issueDescription + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | #{issueDescription} + if actionUrl + tr + td + a(href=actionUrl style='display: block; margin: 1.5rem 3rem 0; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') + span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255,255,255,0.2);') + | View Issue in #{applicationTitle} diff --git a/server/templates/email/media-issue/subject.pug b/server/templates/email/media-issue/subject.pug new file mode 100644 index 000000000..1bf154baa --- /dev/null +++ b/server/templates/email/media-issue/subject.pug @@ -0,0 +1 @@ +!= `${event} - ${mediaName} [${applicationTitle}]` diff --git a/server/templates/email/media-request/html.pug b/server/templates/email/media-request/html.pug index 83db48d4b..334095dfe 100644 --- a/server/templates/email/media-request/html.pug +++ b/server/templates/email/media-request/html.pug @@ -66,4 +66,4 @@ div(style='display: block; background-color: #111827; padding: 2.5rem 0;') td a(href=actionUrl style='display: block; margin: 1.5rem 3rem 0; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255,255,255,0.2);') - | Open in #{applicationTitle} + | View Media in #{applicationTitle} diff --git a/server/templates/email/media-request/subject.pug b/server/templates/email/media-request/subject.pug index a0f50fbab..1bf154baa 100644 --- a/server/templates/email/media-request/subject.pug +++ b/server/templates/email/media-request/subject.pug @@ -1 +1 @@ -!= `${requestType} - ${mediaName} [${applicationTitle}]` +!= `${event} - ${mediaName} [${applicationTitle}]` diff --git a/src/components/CollectionDetails/index.tsx b/src/components/CollectionDetails/index.tsx index 56b368d90..e29526235 100644 --- a/src/components/CollectionDetails/index.tsx +++ b/src/components/CollectionDetails/index.tsx @@ -1,14 +1,11 @@ -import { DownloadIcon, DuplicateIcon } from '@heroicons/react/outline'; -import axios from 'axios'; +import { DownloadIcon } from '@heroicons/react/outline'; import { uniq } from 'lodash'; import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import { MediaStatus } from '../../../server/constants/media'; -import type { MediaRequest } from '../../../server/entity/MediaRequest'; import type { Collection } from '../../../server/models/Collection'; import useSettings from '../../hooks/useSettings'; import { Permission, useUser } from '../../hooks/useUser'; @@ -17,23 +14,17 @@ import Error from '../../pages/_error'; import ButtonWithDropdown from '../Common/ButtonWithDropdown'; import CachedImage from '../Common/CachedImage'; import LoadingSpinner from '../Common/LoadingSpinner'; -import Modal from '../Common/Modal'; import PageTitle from '../Common/PageTitle'; +import RequestModal from '../RequestModal'; import Slider from '../Slider'; import StatusBadge from '../StatusBadge'; import TitleCard from '../TitleCard'; -import Transition from '../Transition'; const messages = defineMessages({ overview: 'Overview', numberofmovies: '{count} Movies', requestcollection: 'Request Collection', - requestswillbecreated: - 'The following titles will have requests created for them:', requestcollection4k: 'Request Collection in 4K', - requestswillbecreated4k: - 'The following titles will have 4K requests created for them:', - requestSuccess: '{title} requested successfully!', }); interface CollectionDetailsProps { @@ -46,10 +37,8 @@ const CollectionDetails: React.FC = ({ const intl = useIntl(); const router = useRouter(); const settings = useSettings(); - const { addToast } = useToasts(); const { hasPermission } = useUser(); const [requestModal, setRequestModal] = useState(false); - const [isRequesting, setRequesting] = useState(false); const [is4k, setIs4k] = useState(false); const { data, error, revalidate } = useSWR( @@ -124,48 +113,6 @@ const CollectionDetails: React.FC = ({ !part.mediaInfo || part.mediaInfo.status4k === MediaStatus.UNKNOWN ).length > 0; - const requestableParts = data.parts.filter( - (part) => - !part.mediaInfo || - part.mediaInfo[is4k ? 'status4k' : 'status'] === MediaStatus.UNKNOWN - ); - - const requestBundle = async () => { - try { - setRequesting(true); - await Promise.all( - requestableParts.map(async (part) => { - await axios.post('/api/v1/request', { - mediaId: part.id, - mediaType: 'movie', - is4k, - }); - }) - ); - - addToast( - - {intl.formatMessage(messages.requestSuccess, { - title: data?.name, - strong: function strong(msg) { - return {msg}; - }, - })} - , - { appearance: 'success', autoDismiss: true } - ); - } catch (e) { - addToast('Something went wrong requesting the collection.', { - appearance: 'error', - autoDismiss: true, - }); - } finally { - setRequesting(false); - setRequestModal(false); - revalidate(); - } - }; - const collectionAttributes: React.ReactNode[] = []; collectionAttributes.push( @@ -229,53 +176,17 @@ const CollectionDetails: React.FC = ({ )} - - requestBundle()} - okText={ - isRequesting - ? intl.formatMessage(globalMessages.requesting) - : intl.formatMessage( - is4k ? globalMessages.request4k : globalMessages.request - ) - } - okDisabled={isRequesting} - okButtonType="primary" - onCancel={() => setRequestModal(false)} - title={intl.formatMessage( - is4k ? messages.requestcollection4k : messages.requestcollection - )} - iconSvg={} - > -

- {intl.formatMessage( - is4k - ? messages.requestswillbecreated4k - : messages.requestswillbecreated - )} -

-
    - {data.parts - .filter( - (part) => - !part.mediaInfo || - part.mediaInfo[is4k ? 'status4k' : 'status'] === - MediaStatus.UNKNOWN - ) - .map((part) => ( -
  • {part.title}
  • - ))} -
-
-
+ type="collection" + is4k={is4k} + onComplete={() => { + revalidate(); + setRequestModal(false); + }} + onCancel={() => setRequestModal(false)} + />
= ({ .map((t, k) => {t}) .reduce((prev, curr) => ( <> - {prev} | {curr} + {prev} + | + {curr} ))} diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 3868e8a40..c897d86f3 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -13,36 +13,36 @@ const Badge: React.FC = ({ children, }) => { const badgeStyle = [ - 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full', + 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap', ]; if (url) { - badgeStyle.push('transition cursor-pointer'); + badgeStyle.push('transition cursor-pointer !no-underline'); } else { badgeStyle.push('cursor-default'); } switch (badgeType) { case 'danger': - badgeStyle.push('bg-red-600 text-red-100'); + badgeStyle.push('bg-red-600 !text-red-100'); if (url) { badgeStyle.push('hover:bg-red-500'); } break; case 'warning': - badgeStyle.push('bg-yellow-500 text-yellow-100'); + badgeStyle.push('bg-yellow-500 !text-yellow-100'); if (url) { badgeStyle.push('hover:bg-yellow-400'); } break; case 'success': - badgeStyle.push('bg-green-500 text-green-100'); + badgeStyle.push('bg-green-500 !text-green-100'); if (url) { badgeStyle.push('hover:bg-green-400'); } break; default: - badgeStyle.push('bg-indigo-500 text-indigo-100'); + badgeStyle.push('bg-indigo-500 !text-indigo-100'); if (url) { badgeStyle.push('hover:bg-indigo-400'); } @@ -54,8 +54,13 @@ const Badge: React.FC = ({ if (url) { return ( - - {children} + + {children} ); } else { diff --git a/src/components/Common/Modal/index.tsx b/src/components/Common/Modal/index.tsx index 08e92d95e..8c39533a8 100644 --- a/src/components/Common/Modal/index.tsx +++ b/src/components/Common/Modal/index.tsx @@ -111,7 +111,7 @@ const Modal: React.FC = ({ }} > {backdrop && ( -
+
void; } diff --git a/src/components/Discover/NetworkSlider/index.tsx b/src/components/Discover/NetworkSlider/index.tsx index 51f742d7d..44c776cf0 100644 --- a/src/components/Discover/NetworkSlider/index.tsx +++ b/src/components/Discover/NetworkSlider/index.tsx @@ -50,6 +50,12 @@ const networks: Network[] = [ 'https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)/tuomPhY2UtuPTqqFnKMVHvSb724.png', url: '/discover/tv/network/49', }, + { + name: 'Discovery+', + image: + 'https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)/1D1bS3Dyw4ScYnFWTlBOvJXC3nb.png', + url: '/discover/tv/network/4353', + }, { name: 'ABC', image: diff --git a/src/components/IssueBlock/index.tsx b/src/components/IssueBlock/index.tsx new file mode 100644 index 000000000..2d3cfb33e --- /dev/null +++ b/src/components/IssueBlock/index.tsx @@ -0,0 +1,69 @@ +import { + CalendarIcon, + ExclamationIcon, + EyeIcon, + UserIcon, +} from '@heroicons/react/solid'; +import Link from 'next/link'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import type Issue from '../../../server/entity/Issue'; +import globalMessages from '../../i18n/globalMessages'; +import Button from '../Common/Button'; +import { issueOptions } from '../IssueModal/constants'; + +interface IssueBlockProps { + issue: Issue; +} + +const IssueBlock: React.FC = ({ issue }) => { + const intl = useIntl(); + const issueOption = issueOptions.find( + (opt) => opt.issueType === issue.issueType + ); + + if (!issueOption) { + return null; + } + + return ( +
+
+
+
+ + + {intl.formatMessage(issueOption.name)} + +
+
+ + + {issue.createdBy.displayName} + +
+
+ + + {intl.formatDate(issue.createdAt, { + year: 'numeric', + month: 'long', + day: 'numeric', + })} + +
+
+
+ + + +
+
+
+ ); +}; + +export default IssueBlock; diff --git a/src/components/IssueDetails/IssueComment/index.tsx b/src/components/IssueDetails/IssueComment/index.tsx new file mode 100644 index 000000000..28e94ee38 --- /dev/null +++ b/src/components/IssueDetails/IssueComment/index.tsx @@ -0,0 +1,269 @@ +import { Menu } from '@headlessui/react'; +import { ExclamationIcon } from '@heroicons/react/outline'; +import { DotsVerticalIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import Link from 'next/link'; +import React, { useState } from 'react'; +import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; +import ReactMarkdown from 'react-markdown'; +import * as Yup from 'yup'; +import type { default as IssueCommentType } from '../../../../server/entity/IssueComment'; +import { Permission, useUser } from '../../../hooks/useUser'; +import Button from '../../Common/Button'; +import Modal from '../../Common/Modal'; +import Transition from '../../Transition'; + +const messages = defineMessages({ + postedby: 'Posted {relativeTime} by {username}', + postedbyedited: 'Posted {relativeTime} by {username} (Edited)', + delete: 'Delete Comment', + areyousuredelete: 'Are you sure you want to delete this comment?', + validationComment: 'You must enter a message', + edit: 'Edit Comment', +}); + +interface IssueCommentProps { + comment: IssueCommentType; + isReversed?: boolean; + isActiveUser?: boolean; + onUpdate?: () => void; +} + +const IssueComment: React.FC = ({ + comment, + isReversed = false, + isActiveUser = false, + onUpdate, +}) => { + const intl = useIntl(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const { hasPermission } = useUser(); + + const EditCommentSchema = Yup.object().shape({ + newMessage: Yup.string().required( + intl.formatMessage(messages.validationComment) + ), + }); + + const deleteComment = async () => { + try { + await axios.delete(`/api/v1/issueComment/${comment.id}`); + } catch (e) { + // something went wrong deleting the comment + } finally { + if (onUpdate) { + onUpdate(); + } + } + }; + + return ( +
+ + setShowDeleteModal(false)} + onOk={() => deleteComment()} + okText={intl.formatMessage(messages.delete)} + okButtonType="danger" + iconSvg={} + > + {intl.formatMessage(messages.areyousuredelete)} + + + + + + + +
+
+ {(isActiveUser || hasPermission(Permission.MANAGE_ISSUES)) && ( + + {({ open }) => ( + <> +
+ + Open options + +
+ + + +
+ {isActiveUser && ( + + {({ active }) => ( + + )} + + )} + + {({ active }) => ( + + )} + +
+
+
+ + )} +
+ )} +
+
+ {isEditing ? ( + { + await axios.put(`/api/v1/issueComment/${comment.id}`, { + message: values.newMessage, + }); + + if (onUpdate) { + onUpdate(); + } + + setIsEditing(false); + }} + validationSchema={EditCommentSchema} + > + {({ isValid, isSubmitting, errors, touched }) => { + return ( +
+ + {errors.newMessage && touched.newMessage && ( +
{errors.newMessage}
+ )} +
+ + +
+ + ); + }} +
+ ) : ( +
+ + {comment.message} + +
+ )} +
+
+
+ + {intl.formatMessage( + comment.createdAt !== comment.updatedAt + ? messages.postedbyedited + : messages.postedby, + { + username: ( + + + {comment.user.displayName} + + + ), + relativeTime: ( + + ), + } + )} + +
+
+
+ ); +}; + +export default IssueComment; diff --git a/src/components/IssueDetails/IssueDescription/index.tsx b/src/components/IssueDetails/IssueDescription/index.tsx new file mode 100644 index 000000000..f41386366 --- /dev/null +++ b/src/components/IssueDetails/IssueDescription/index.tsx @@ -0,0 +1,157 @@ +import { Menu, Transition } from '@headlessui/react'; +import { DotsVerticalIcon } from '@heroicons/react/solid'; +import { Field, Form, Formik } from 'formik'; +import React, { Fragment, useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import ReactMarkdown from 'react-markdown'; +import { Permission, useUser } from '../../../hooks/useUser'; +import globalMessages from '../../../i18n/globalMessages'; +import Button from '../../Common/Button'; + +const messages = defineMessages({ + description: 'Description', + edit: 'Edit Description', + deleteissue: 'Delete Issue', +}); + +interface IssueDescriptionProps { + description: string; + belongsToUser: boolean; + commentCount: number; + onEdit: (newDescription: string) => void; + onDelete: () => void; +} + +const IssueDescription: React.FC = ({ + description, + belongsToUser, + commentCount, + onEdit, + onDelete, +}) => { + const intl = useIntl(); + const { hasPermission } = useUser(); + const [isEditing, setIsEditing] = useState(false); + + return ( +
+
+
+ {intl.formatMessage(messages.description)} +
+ {(hasPermission(Permission.MANAGE_ISSUES) || belongsToUser) && ( + + {({ open }) => ( + <> +
+ + Open options + +
+ + + +
+ {belongsToUser && ( + + {({ active }) => ( + + )} + + )} + {(hasPermission(Permission.MANAGE_ISSUES) || + !commentCount) && ( + + {({ active }) => ( + + )} + + )} +
+
+
+ + )} +
+ )} +
+ {isEditing ? ( + { + onEdit(values.newMessage); + setIsEditing(false); + }} + > + {() => { + return ( +
+ +
+ + +
+ + ); + }} +
+ ) : ( +
+ + {description} + +
+ )} +
+ ); +}; + +export default IssueDescription; diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx new file mode 100644 index 000000000..73728c802 --- /dev/null +++ b/src/components/IssueDetails/index.tsx @@ -0,0 +1,664 @@ +import { + ChatIcon, + CheckCircleIcon, + ExclamationIcon, + PlayIcon, + ServerIcon, +} from '@heroicons/react/outline'; +import { RefreshIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import React, { useState } from 'react'; +import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import { IssueStatus } from '../../../server/constants/issue'; +import { MediaType } from '../../../server/constants/media'; +import type Issue from '../../../server/entity/Issue'; +import type { MovieDetails } from '../../../server/models/Movie'; +import type { TvDetails } from '../../../server/models/Tv'; +import { Permission, useUser } from '../../hooks/useUser'; +import globalMessages from '../../i18n/globalMessages'; +import Error from '../../pages/_error'; +import Badge from '../Common/Badge'; +import Button from '../Common/Button'; +import CachedImage from '../Common/CachedImage'; +import LoadingSpinner from '../Common/LoadingSpinner'; +import Modal from '../Common/Modal'; +import PageTitle from '../Common/PageTitle'; +import { issueOptions } from '../IssueModal/constants'; +import Transition from '../Transition'; +import IssueComment from './IssueComment'; +import IssueDescription from './IssueDescription'; + +const messages = defineMessages({ + openedby: '#{issueId} opened {relativeTime} by {username}', + closeissue: 'Close Issue', + closeissueandcomment: 'Close with Comment', + leavecomment: 'Comment', + comments: 'Comments', + reopenissue: 'Reopen Issue', + reopenissueandcomment: 'Reopen with Comment', + issuepagetitle: 'Issue', + playonplex: 'Play on Plex', + play4konplex: 'Play in 4K on Plex', + openinarr: 'Open in {arr}', + openin4karr: 'Open in 4K {arr}', + toasteditdescriptionsuccess: 'Issue description edited successfully!', + toasteditdescriptionfailed: + 'Something went wrong while editing the issue description.', + toaststatusupdated: 'Issue status updated successfully!', + toaststatusupdatefailed: + 'Something went wrong while updating the issue status.', + issuetype: 'Type', + lastupdated: 'Last Updated', + problemseason: 'Affected Season', + allseasons: 'All Seasons', + season: 'Season {seasonNumber}', + problemepisode: 'Affected Episode', + allepisodes: 'All Episodes', + episode: 'Episode {episodeNumber}', + deleteissue: 'Delete Issue', + deleteissueconfirm: 'Are you sure you want to delete this issue?', + toastissuedeleted: 'Issue deleted successfully!', + toastissuedeletefailed: 'Something went wrong while deleting the issue.', + nocomments: 'No comments.', + unknownissuetype: 'Unknown', + commentplaceholder: 'Add a comment…', +}); + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +const IssueDetails: React.FC = () => { + const { addToast } = useToasts(); + const router = useRouter(); + const intl = useIntl(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const { user: currentUser, hasPermission } = useUser(); + const { data: issueData, revalidate: revalidateIssue } = useSWR( + `/api/v1/issue/${router.query.issueId}` + ); + const { data, error } = useSWR( + issueData?.media.tmdbId + ? `/api/v1/${issueData.media.mediaType}/${issueData.media.tmdbId}` + : null + ); + + const CommentSchema = Yup.object().shape({ + message: Yup.string().required(), + }); + + const issueOption = issueOptions.find( + (opt) => opt.issueType === issueData?.issueType + ); + + if (!data && !error) { + return ; + } + + if (!data || !issueData) { + return ; + } + + const belongsToUser = issueData.createdBy.id === currentUser?.id; + + const [firstComment, ...otherComments] = issueData.comments; + + const editFirstComment = async (newMessage: string) => { + try { + await axios.put(`/api/v1/issueComment/${firstComment.id}`, { + message: newMessage, + }); + + addToast(intl.formatMessage(messages.toasteditdescriptionsuccess), { + appearance: 'success', + autoDismiss: true, + }); + revalidateIssue(); + } catch (e) { + addToast(intl.formatMessage(messages.toasteditdescriptionfailed), { + appearance: 'error', + autoDismiss: true, + }); + } + }; + + const updateIssueStatus = async (newStatus: 'open' | 'resolved') => { + try { + await axios.post(`/api/v1/issue/${issueData.id}/${newStatus}`); + + addToast(intl.formatMessage(messages.toaststatusupdated), { + appearance: 'success', + autoDismiss: true, + }); + revalidateIssue(); + } catch (e) { + addToast(intl.formatMessage(messages.toaststatusupdatefailed), { + appearance: 'error', + autoDismiss: true, + }); + } + }; + + const deleteIssue = async () => { + try { + await axios.delete(`/api/v1/issue/${issueData.id}`); + + addToast(intl.formatMessage(messages.toastissuedeleted), { + appearance: 'success', + autoDismiss: true, + }); + router.push('/issues'); + } catch (e) { + addToast(intl.formatMessage(messages.toastissuedeletefailed), { + appearance: 'error', + autoDismiss: true, + }); + } + }; + + const title = isMovie(data) ? data.title : data.name; + const releaseYear = isMovie(data) ? data.releaseDate : data.firstAirDate; + + return ( +
+ + + setShowDeleteModal(false)} + onOk={() => deleteIssue()} + okText={intl.formatMessage(messages.deleteissue)} + okButtonType="danger" + iconSvg={} + > + {intl.formatMessage(messages.deleteissueconfirm)} + + + {data.backdropPath && ( +
+ +
+
+ )} +
+
+ +
+
+
+ {issueData.status === IssueStatus.OPEN && ( + + {intl.formatMessage(globalMessages.open)} + + )} + {issueData.status === IssueStatus.RESOLVED && ( + + {intl.formatMessage(globalMessages.resolved)} + + )} +
+

+ + {title} + {' '} + {releaseYear && ( + ({releaseYear.slice(0, 4)}) + )} +

+ + {intl.formatMessage(messages.openedby, { + issueId: issueData.id, + username: ( + + + + + {issueData.createdBy.displayName} + + + + ), + relativeTime: ( + + ), + })} + +
+
+
+
+ { + editFirstComment(newMessage); + }} + onDelete={() => setShowDeleteModal(true)} + /> +
+
+
+ {intl.formatMessage(messages.issuetype)} + + {intl.formatMessage( + issueOption?.name ?? messages.unknownissuetype + )} + +
+ {issueData.media.mediaType === MediaType.TV && ( + <> +
+ {intl.formatMessage(messages.problemseason)} + + {intl.formatMessage( + issueData.problemSeason > 0 + ? messages.season + : messages.allseasons, + { seasonNumber: issueData.problemSeason } + )} + +
+ {issueData.problemSeason > 0 && ( +
+ {intl.formatMessage(messages.problemepisode)} + + {intl.formatMessage( + issueData.problemEpisode > 0 + ? messages.episode + : messages.allepisodes, + { episodeNumber: issueData.problemEpisode } + )} + +
+ )} + + )} +
+ {intl.formatMessage(messages.lastupdated)} + + + +
+
+
+ {issueData?.media.plexUrl && ( + + )} + {issueData?.media.serviceUrl && hasPermission(Permission.ADMIN) && ( + + )} + {issueData?.media.plexUrl4k && ( + + )} + {issueData?.media.serviceUrl4k && + hasPermission(Permission.ADMIN) && ( + + )} +
+
+
+
+ {intl.formatMessage(messages.comments)} +
+ {otherComments.map((comment) => ( + revalidateIssue()} + /> + ))} + {otherComments.length === 0 && ( +
+ {intl.formatMessage(messages.nocomments)} +
+ )} + {(hasPermission(Permission.MANAGE_ISSUES) || belongsToUser) && ( + { + await axios.post(`/api/v1/issue/${issueData?.id}/comment`, { + message: values.message, + }); + revalidateIssue(); + resetForm(); + }} + > + {({ isValid, isSubmitting, values, handleSubmit }) => { + return ( +
+
+ +
+ {hasPermission(Permission.MANAGE_ISSUES) && ( + <> + {issueData.status === IssueStatus.OPEN ? ( + + ) : ( + + )} + + )} + +
+
+
+ ); + }} +
+ )} +
+
+
+
+
+ {intl.formatMessage(messages.issuetype)} + + {intl.formatMessage( + issueOption?.name ?? messages.unknownissuetype + )} + +
+ {issueData.media.mediaType === MediaType.TV && ( + <> +
+ {intl.formatMessage(messages.problemseason)} + + {intl.formatMessage( + issueData.problemSeason > 0 + ? messages.season + : messages.allseasons, + { seasonNumber: issueData.problemSeason } + )} + +
+ {issueData.problemSeason > 0 && ( +
+ {intl.formatMessage(messages.problemepisode)} + + {intl.formatMessage( + issueData.problemEpisode > 0 + ? messages.episode + : messages.allepisodes, + { episodeNumber: issueData.problemEpisode } + )} + +
+ )} + + )} +
+ {intl.formatMessage(messages.lastupdated)} + + + +
+
+
+ {issueData?.media.plexUrl && ( + + )} + {issueData?.media.serviceUrl && hasPermission(Permission.ADMIN) && ( + + )} + {issueData?.media.plexUrl4k && ( + + )} + {issueData?.media.serviceUrl4k && hasPermission(Permission.ADMIN) && ( + + )} +
+
+
+
+ ); +}; + +export default IssueDetails; diff --git a/src/components/IssueList/IssueItem/index.tsx b/src/components/IssueList/IssueItem/index.tsx new file mode 100644 index 000000000..267941fc2 --- /dev/null +++ b/src/components/IssueList/IssueItem/index.tsx @@ -0,0 +1,275 @@ +import { EyeIcon } from '@heroicons/react/solid'; +import Link from 'next/link'; +import React from 'react'; +import { useInView } from 'react-intersection-observer'; +import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; +import useSWR from 'swr'; +import { IssueStatus } from '../../../../server/constants/issue'; +import { MediaType } from '../../../../server/constants/media'; +import Issue from '../../../../server/entity/Issue'; +import { MovieDetails } from '../../../../server/models/Movie'; +import { TvDetails } from '../../../../server/models/Tv'; +import { Permission, useUser } from '../../../hooks/useUser'; +import globalMessages from '../../../i18n/globalMessages'; +import Badge from '../../Common/Badge'; +import Button from '../../Common/Button'; +import CachedImage from '../../Common/CachedImage'; +import { issueOptions } from '../../IssueModal/constants'; + +const messages = defineMessages({ + openeduserdate: '{date} by {user}', + seasons: '{seasonCount, plural, one {Season} other {Seasons}}', + episodes: '{episodeCount, plural, one {Episode} other {Episodes}}', + problemepisode: 'Affected Episode', + issuetype: 'Type', + issuestatus: 'Status', + opened: 'Opened', + viewissue: 'View Issue', + unknownissuetype: 'Unknown', +}); + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +interface IssueItemProps { + issue: Issue; +} + +const IssueItem: React.FC = ({ issue }) => { + const intl = useIntl(); + const { hasPermission } = useUser(); + const { ref, inView } = useInView({ + triggerOnce: true, + }); + const url = + issue.media.mediaType === 'movie' + ? `/api/v1/movie/${issue.media.tmdbId}` + : `/api/v1/tv/${issue.media.tmdbId}`; + const { data: title, error } = useSWR( + inView ? url : null + ); + + if (!title && !error) { + return ( +
+ ); + } + + if (!title) { + return
uh oh
; + } + + const issueOption = issueOptions.find( + (opt) => opt.issueType === issue?.issueType + ); + + const problemSeasonEpisodeLine: React.ReactNode[] = []; + + if (!isMovie(title) && issue) { + problemSeasonEpisodeLine.push( + <> + + {intl.formatMessage(messages.seasons, { + seasonCount: issue.problemSeason ? 1 : 0, + })} + + + + {issue.problemSeason > 0 + ? issue.problemSeason + : intl.formatMessage(globalMessages.all)} + + + + ); + + if (issue.problemSeason > 0) { + problemSeasonEpisodeLine.push( + <> + + {intl.formatMessage(messages.episodes, { + episodeCount: issue.problemEpisode ? 1 : 0, + })} + + + + {issue.problemEpisode > 0 + ? issue.problemEpisode + : intl.formatMessage(globalMessages.all)} + + + + ); + } + } + + return ( +
+ {title.backdropPath && ( +
+ +
+
+ )} +
+
+ + + + + +
+
+ {(isMovie(title) ? title.releaseDate : title.firstAirDate)?.slice( + 0, + 4 + )} +
+ + + {isMovie(title) ? title.title : title.name} + + + {problemSeasonEpisodeLine.length > 0 && ( +
+ {problemSeasonEpisodeLine.map((t, k) => ( + {t} + ))} +
+ )} +
+
+
+
+ + {intl.formatMessage(messages.issuestatus)} + + {issue.status === IssueStatus.OPEN ? ( + + {intl.formatMessage(globalMessages.open)} + + ) : ( + + {intl.formatMessage(globalMessages.resolved)} + + )} +
+
+ + {intl.formatMessage(messages.issuetype)} + + + {intl.formatMessage( + issueOption?.name ?? messages.unknownissuetype + )} + +
+
+ {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { + type: 'or', + }) ? ( + <> + + {intl.formatMessage(messages.opened)} + + + {intl.formatMessage(messages.openeduserdate, { + date: ( + + ), + user: ( + + + + + {issue.createdBy.displayName} + + + + ), + })} + + + ) : ( + <> + + {intl.formatMessage(messages.opened)} + + + + + + )} +
+
+
+
+ + + + + +
+
+ ); +}; + +export default IssueItem; diff --git a/src/components/IssueList/index.tsx b/src/components/IssueList/index.tsx new file mode 100644 index 000000000..a78a762ca --- /dev/null +++ b/src/components/IssueList/index.tsx @@ -0,0 +1,256 @@ +import { + ChevronLeftIcon, + ChevronRightIcon, + FilterIcon, + SortDescendingIcon, +} from '@heroicons/react/solid'; +import { useRouter } from 'next/router'; +import React, { useEffect, useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import useSWR from 'swr'; +import { IssueResultsResponse } from '../../../server/interfaces/api/issueInterfaces'; +import Button from '../../components/Common/Button'; +import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams'; +import globalMessages from '../../i18n/globalMessages'; +import Header from '../Common/Header'; +import LoadingSpinner from '../Common/LoadingSpinner'; +import PageTitle from '../Common/PageTitle'; +import IssueItem from './IssueItem'; + +const messages = defineMessages({ + issues: 'Issues', + sortAdded: 'Most Recent', + sortModified: 'Last Modified', + showallissues: 'Show All Issues', +}); + +enum Filter { + ALL = 'all', + OPEN = 'open', + RESOLVED = 'resolved', +} + +type Sort = 'added' | 'modified'; + +const IssueList: React.FC = () => { + const intl = useIntl(); + const router = useRouter(); + const [currentFilter, setCurrentFilter] = useState(Filter.OPEN); + const [currentSort, setCurrentSort] = useState('added'); + const [currentPageSize, setCurrentPageSize] = useState(10); + + const page = router.query.page ? Number(router.query.page) : 1; + const pageIndex = page - 1; + const updateQueryParams = useUpdateQueryParams({ page: page.toString() }); + + const { data, error } = useSWR( + `/api/v1/issue?take=${currentPageSize}&skip=${ + pageIndex * currentPageSize + }&filter=${currentFilter}&sort=${currentSort}` + ); + + // Restore last set filter values on component mount + useEffect(() => { + const filterString = window.localStorage.getItem('il-filter-settings'); + + if (filterString) { + const filterSettings = JSON.parse(filterString); + + setCurrentFilter(filterSettings.currentFilter); + setCurrentSort(filterSettings.currentSort); + setCurrentPageSize(filterSettings.currentPageSize); + } + + // If filter value is provided in query, use that instead + if (Object.values(Filter).includes(router.query.filter as Filter)) { + setCurrentFilter(router.query.filter as Filter); + } + }, [router.query.filter]); + + // Set filter values to local storage any time they are changed + useEffect(() => { + window.localStorage.setItem( + 'il-filter-settings', + JSON.stringify({ + currentFilter, + currentSort, + currentPageSize, + }) + ); + }, [currentFilter, currentSort, currentPageSize]); + + if (!data && !error) { + return ; + } + + if (!data) { + return ; + } + + const hasNextPage = data.pageInfo.pages > pageIndex + 1; + const hasPrevPage = pageIndex > 0; + + return ( + <> + +
+
{intl.formatMessage(messages.issues)}
+
+
+ + + + +
+
+ + + + +
+
+
+ {data.results.map((issue) => { + return ( +
+ +
+ ); + })} + {data.results.length === 0 && ( +
+ + {intl.formatMessage(globalMessages.noresults)} + + {currentFilter !== Filter.ALL && ( +
+ +
+ )} +
+ )} +
+ +
+ + ); +}; + +export default IssueList; diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx new file mode 100644 index 000000000..c2d75e243 --- /dev/null +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -0,0 +1,329 @@ +import { RadioGroup } from '@headlessui/react'; +import { ExclamationIcon } from '@heroicons/react/outline'; +import { ArrowCircleRightIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import { Field, Formik } from 'formik'; +import Link from 'next/link'; +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import { MediaStatus } from '../../../../server/constants/media'; +import type Issue from '../../../../server/entity/Issue'; +import { MovieDetails } from '../../../../server/models/Movie'; +import { TvDetails } from '../../../../server/models/Tv'; +import useSettings from '../../../hooks/useSettings'; +import { Permission, useUser } from '../../../hooks/useUser'; +import globalMessages from '../../../i18n/globalMessages'; +import Button from '../../Common/Button'; +import Modal from '../../Common/Modal'; +import { issueOptions } from '../constants'; + +const messages = defineMessages({ + validationMessageRequired: 'You must provide a description', + issomethingwrong: 'Is there a problem with {title}?', + whatswrong: "What's wrong?", + providedetail: + 'Please provide a detailed explanation of the issue you encountered.', + extras: 'Extras', + season: 'Season {seasonNumber}', + episode: 'Episode {episodeNumber}', + allseasons: 'All Seasons', + allepisodes: 'All Episodes', + problemseason: 'Affected Season', + problemepisode: 'Affected Episode', + toastSuccessCreate: + 'Issue report for {title} submitted successfully!', + toastFailedCreate: 'Something went wrong while submitting the issue.', + toastviewissue: 'View Issue', + reportissue: 'Report an Issue', + submitissue: 'Submit Issue', +}); + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +const classNames = (...classes: string[]) => { + return classes.filter(Boolean).join(' '); +}; + +interface CreateIssueModalProps { + mediaType: 'movie' | 'tv'; + tmdbId?: number; + onCancel?: () => void; +} + +const CreateIssueModal: React.FC = ({ + onCancel, + mediaType, + tmdbId, +}) => { + const intl = useIntl(); + const settings = useSettings(); + const { hasPermission } = useUser(); + const { addToast } = useToasts(); + const { data, error } = useSWR( + tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null + ); + + if (!tmdbId) { + return null; + } + + const availableSeasons = (data?.mediaInfo?.seasons ?? []) + .filter( + (season) => + season.status === MediaStatus.AVAILABLE || + season.status === MediaStatus.PARTIALLY_AVAILABLE || + (settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && + (season.status4k === MediaStatus.AVAILABLE || + season.status4k === MediaStatus.PARTIALLY_AVAILABLE)) + ) + .map((season) => season.seasonNumber); + + const CreateIssueModalSchema = Yup.object().shape({ + message: Yup.string().required( + intl.formatMessage(messages.validationMessageRequired) + ), + }); + + return ( + { + try { + const newIssue = await axios.post('/api/v1/issue', { + issueType: values.selectedIssue.issueType, + message: values.message, + mediaId: data?.mediaInfo?.id, + problemSeason: values.problemSeason, + problemEpisode: + values.problemSeason > 0 ? values.problemEpisode : 0, + }); + + if (data) { + addToast( + <> +
+ {intl.formatMessage(messages.toastSuccessCreate, { + title: isMovie(data) ? data.title : data.name, + strong: function strong(msg) { + return {msg}; + }, + })} +
+ + + + , + { + appearance: 'success', + autoDismiss: true, + } + ); + } + + if (onCancel) { + onCancel(); + } + } catch (e) { + addToast(intl.formatMessage(messages.toastFailedCreate), { + appearance: 'error', + autoDismiss: true, + }); + } + }} + > + {({ handleSubmit, values, setFieldValue, errors, touched }) => { + return ( + } + title={intl.formatMessage(messages.reportissue)} + cancelText={intl.formatMessage(globalMessages.close)} + onOk={() => handleSubmit()} + okText={intl.formatMessage(messages.submitissue)} + loading={!data && !error} + backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} + > + {data && ( +
+ + {intl.formatMessage(messages.issomethingwrong, { + title: isMovie(data) ? data.title : data.name, + })} + +
+ )} + {mediaType === 'tv' && data && !isMovie(data) && ( + <> +
+ +
+
+ + {availableSeasons.length > 1 && ( + + )} + {availableSeasons.map((season) => ( + + ))} + +
+
+
+ {values.problemSeason > 0 && ( +
+ +
+
+ + + {[ + ...Array( + data.seasons.find( + (season) => + Number(values.problemSeason) === + season.seasonNumber + )?.episodeCount ?? 0 + ), + ].map((i, index) => ( + + ))} + +
+
+
+ )} + + )} + setFieldValue('selectedIssue', issue)} + className="mt-4" + > + + Select an Issue + +
+ {issueOptions.map((setting, index) => ( + + classNames( + index === 0 ? 'rounded-tl-md rounded-tr-md' : '', + index === issueOptions.length - 1 + ? 'rounded-bl-md rounded-br-md' + : '', + checked + ? 'bg-indigo-600 border-indigo-500 z-10' + : 'border-gray-500', + 'relative border p-4 flex cursor-pointer focus:outline-none' + ) + } + > + {({ active, checked }) => ( + <> + + ))} +
+
+
+ + + {errors.message && touched.message && ( +
{errors.message}
+ )} +
+
+ ); + }} +
+ ); +}; + +export default CreateIssueModal; diff --git a/src/components/IssueModal/constants.ts b/src/components/IssueModal/constants.ts new file mode 100644 index 000000000..92cf6bc77 --- /dev/null +++ b/src/components/IssueModal/constants.ts @@ -0,0 +1,34 @@ +import { defineMessages, MessageDescriptor } from 'react-intl'; +import { IssueType } from '../../../server/constants/issue'; + +const messages = defineMessages({ + issueAudio: 'Audio', + issueVideo: 'Video', + issueSubtitles: 'Subtitle', + issueOther: 'Other', +}); + +interface IssueOption { + name: MessageDescriptor; + issueType: IssueType; + mediaType?: 'movie' | 'tv'; +} + +export const issueOptions: IssueOption[] = [ + { + name: messages.issueVideo, + issueType: IssueType.VIDEO, + }, + { + name: messages.issueAudio, + issueType: IssueType.AUDIO, + }, + { + name: messages.issueSubtitles, + issueType: IssueType.SUBTITLES, + }, + { + name: messages.issueOther, + issueType: IssueType.OTHER, + }, +]; diff --git a/src/components/IssueModal/index.tsx b/src/components/IssueModal/index.tsx new file mode 100644 index 000000000..f3f226de9 --- /dev/null +++ b/src/components/IssueModal/index.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import Transition from '../Transition'; +import CreateIssueModal from './CreateIssueModal'; + +interface IssueModalProps { + show?: boolean; + onCancel: () => void; + mediaType: 'movie' | 'tv'; + tmdbId: number; + issueId?: never; +} + +const IssueModal: React.FC = ({ + show, + mediaType, + onCancel, + tmdbId, +}) => ( + + + +); + +export default IssueModal; diff --git a/src/components/Layout/SearchInput/index.tsx b/src/components/Layout/SearchInput/index.tsx index db9a833cc..619c3375d 100644 --- a/src/components/Layout/SearchInput/index.tsx +++ b/src/components/Layout/SearchInput/index.tsx @@ -27,7 +27,6 @@ const SearchInput: React.FC = () => { className="block w-full py-2 pl-10 text-white placeholder-gray-300 bg-gray-900 border border-gray-600 rounded-full bg-opacity-80 focus:bg-opacity-100 focus:border-gray-500 hover:border-gray-500 focus:outline-none focus:ring-0 focus:placeholder-gray-400 sm:text-base" placeholder={intl.formatMessage(messages.searchPlaceholder)} type="search" - inputMode="search" value={searchValue} onChange={(e) => setSearchValue(e.target.value)} onFocus={() => setIsOpen(true)} @@ -36,6 +35,12 @@ const SearchInput: React.FC = () => { setIsOpen(false); } }} + onKeyUp={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + (e.target as HTMLInputElement).blur(); + } + }} /> {searchValue.length > 0 && ( +
+ )} + {data?.mediaInfo && + data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && + settings.currentSettings.series4kEnabled && ( +
+ +
+ )} + {mediaType === 'tv' && ( +
+ {intl.formatMessage(messages.allseasonsmarkedavailable)} +
+ )} +
+ )} + {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { + type: 'or', + }) && + openIssues.length > 0 && ( + <> +

+ {intl.formatMessage(messages.manageModalIssues)} +

+
+
    + {openIssues.map((issue) => ( +
  • + +
  • + ))} +
+
+ + )} +

+ {intl.formatMessage(messages.manageModalRequests)} +

+
+
    + {data.mediaInfo?.requests?.map((request) => ( +
  • + revalidate()} /> +
  • + ))} + {(data.mediaInfo?.requests ?? []).length === 0 && ( +
  • + {intl.formatMessage(messages.manageModalNoRequests)} +
  • + )} +
+
+ {hasPermission(Permission.ADMIN) && + (data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && ( + + )} + {data?.mediaInfo && ( +
+ deleteMedia()} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + {intl.formatMessage(messages.manageModalClearMedia)} + +
+ {intl.formatMessage(messages.manageModalClearMediaWarning, { + mediaType: intl.formatMessage( + mediaType === 'movie' ? messages.movie : messages.tvshow + ), + })} +
+
+ )} + + ); +}; + +export default ManageSlideOver; diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index b1d5bcdf9..443981691 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -2,18 +2,17 @@ import { ArrowCircleRightIcon, CloudIcon, CogIcon, + ExclamationIcon, FilmIcon, PlayIcon, TicketIcon, } from '@heroicons/react/outline'; import { - CheckCircleIcon, ChevronDoubleDownIcon, ChevronDoubleUpIcon, - DocumentRemoveIcon, - ExternalLinkIcon, } from '@heroicons/react/solid'; -import axios from 'axios'; +import { hasFlag } from 'country-flag-icons'; +import 'country-flag-icons/3x2/flags.css'; import { uniqBy } from 'lodash'; import Link from 'next/link'; import { useRouter } from 'next/router'; @@ -21,6 +20,7 @@ import React, { useMemo, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import type { RTRating } from '../../../server/api/rottentomatoes'; +import { IssueStatus } from '../../../server/constants/issue'; import { MediaStatus } from '../../../server/constants/media'; import type { MovieDetails as MovieDetailsType } from '../../../server/models/Movie'; import RTAudFresh from '../../assets/rt_aud_fresh.svg'; @@ -36,16 +36,14 @@ import Error from '../../pages/_error'; import { sortCrewPriority } from '../../utils/creditHelpers'; import Button from '../Common/Button'; import CachedImage from '../Common/CachedImage'; -import ConfirmButton from '../Common/ConfirmButton'; import LoadingSpinner from '../Common/LoadingSpinner'; import PageTitle from '../Common/PageTitle'; import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; -import SlideOver from '../Common/SlideOver'; -import DownloadBlock from '../DownloadBlock'; import ExternalLinkBlock from '../ExternalLinkBlock'; +import IssueModal from '../IssueModal'; +import ManageSlideOver from '../ManageSlideOver'; import MediaSlider from '../MediaSlider'; import PersonCard from '../PersonCard'; -import RequestBlock from '../RequestBlock'; import RequestButton from '../RequestButton'; import Slider from '../Slider'; import StatusBadge from '../StatusBadge'; @@ -64,17 +62,8 @@ const messages = defineMessages({ recommendations: 'Recommendations', similar: 'Similar Titles', overviewunavailable: 'Overview unavailable.', - manageModalTitle: 'Manage Movie', - manageModalRequests: 'Requests', - manageModalNoRequests: 'No requests.', - manageModalClearMedia: 'Clear Media Data', - manageModalClearMediaWarning: - '* This will irreversibly remove all data for this movie, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.', studio: '{studioCount, plural, one {Studio} other {Studios}}', viewfullcrew: 'View Full Crew', - openradarr: 'Open Movie in Radarr', - openradarr4k: 'Open Movie in 4K Radarr', - downloadstatus: 'Download Status', playonplex: 'Play on Plex', play4konplex: 'Play in 4K on Plex', markavailable: 'Mark as Available', @@ -82,6 +71,8 @@ const messages = defineMessages({ showmore: 'Show More', showless: 'Show Less', streamingproviders: 'Currently Streaming On', + productioncountries: + 'Production {countryCount, plural, one {Country} other {Countries}}', }); interface MovieDetailsProps { @@ -97,6 +88,7 @@ const MovieDetails: React.FC = ({ movie }) => { const [showManager, setShowManager] = useState(false); const minStudios = 3; const [showMoreStudios, setShowMoreStudios] = useState(false); + const [showIssueModal, setShowIssueModal] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/movie/${router.query.movieId}`, @@ -164,20 +156,6 @@ const MovieDetails: React.FC = ({ movie }) => { }); } - const deleteMedia = async () => { - if (data?.mediaInfo?.id) { - await axios.delete(`/api/v1/media/${data?.mediaInfo?.id}`); - revalidate(); - } - }; - - const markAvailable = async (is4k = false) => { - await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, { - is4k, - }); - revalidate(); - }; - const region = user?.settings?.region ? user.settings.region : settings.currentSettings.region @@ -264,141 +242,19 @@ const MovieDetails: React.FC = ({ movie }) => {
)} - setShowIssueModal(false)} + show={showIssueModal} + mediaType="movie" + tmdbId={data.id} + /> + setShowManager(false)} - subText={data.title} - > - {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || - (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( - <> -

- {intl.formatMessage(messages.downloadstatus)} -

-
-
    - {data.mediaInfo?.downloadStatus?.map((status, index) => ( -
  • - -
  • - ))} - {data.mediaInfo?.downloadStatus4k?.map((status, index) => ( -
  • - -
  • - ))} -
-
- - )} - {data?.mediaInfo && - (data.mediaInfo.status !== MediaStatus.AVAILABLE || - (data.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.movie4kEnabled)) && ( -
- {data?.mediaInfo && - data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( -
- -
- )} - {data?.mediaInfo && - data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.movie4kEnabled && ( -
- -
- )} -
- )} -

- {intl.formatMessage(messages.manageModalRequests)} -

-
-
    - {data.mediaInfo?.requests?.map((request) => ( -
  • - revalidate()} /> -
  • - ))} - {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • - {intl.formatMessage(messages.manageModalNoRequests)} -
  • - )} -
-
- {(data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && ( -
- {data?.mediaInfo?.serviceUrl && ( - - - - )} - {data?.mediaInfo?.serviceUrl4k && ( - - - - )} -
- )} - {data?.mediaInfo && ( -
- deleteMedia()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.manageModalClearMedia)} - -
- {intl.formatMessage(messages.manageModalClearMediaWarning)} -
-
- )} -
+ revalidate={() => revalidate()} + show={showManager} + />
= ({ movie }) => { .map((t, k) => {t}) .reduce((prev, curr) => ( <> - {prev} | {curr} + {prev} + | + {curr} ))} @@ -475,13 +333,52 @@ const MovieDetails: React.FC = ({ movie }) => { tmdbId={data.id} onUpdate={() => revalidate()} /> + {(data.mediaInfo?.status === MediaStatus.AVAILABLE || + (settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { + type: 'or', + } + ) && + data.mediaInfo?.status4k === MediaStatus.AVAILABLE)) && + hasPermission( + [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], + { + type: 'or', + } + ) && ( + + )} {hasPermission(Permission.MANAGE_REQUESTS) && ( )}
@@ -703,6 +600,37 @@ const MovieDetails: React.FC = ({ movie }) => {
)} + {data.productionCountries.length > 0 && ( +
+ + {intl.formatMessage(messages.productioncountries, { + countryCount: data.productionCountries.length, + })} + + + {data.productionCountries.map((c) => { + return ( + + {hasFlag(c.iso_3166_1) && ( + + )} + + {intl.formatDisplayName(c.iso_3166_1, { + type: 'region', + fallback: 'none', + }) ?? c.name} + + + ); + })} + +
+ )} {data.productionCompanies.length > 0 && (
diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 0b71f6709..567ce052a 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -37,6 +37,28 @@ const messages = defineMessages({ 'Send notifications when media requests are declined.', usermediadeclinedDescription: 'Get notified when your media requests are declined.', + issuecreated: 'Issue Reported', + issuecreatedDescription: 'Send notifications when issues are reported.', + userissuecreatedDescription: 'Get notified when other users report issues.', + issuecomment: 'Issue Comment', + issuecommentDescription: + 'Send notifications when issues receive new comments.', + userissuecommentDescription: + 'Get notified when issues you reported receive new comments.', + adminissuecommentDescription: + 'Get notified when other users comment on issues.', + issueresolved: 'Issue Resolved', + issueresolvedDescription: 'Send notifications when issues are resolved.', + userissueresolvedDescription: + 'Get notified when issues you reported are resolved.', + adminissueresolvedDescription: + 'Get notified when issues are resolved by other users.', + issuereopened: 'Issue Reopened', + issuereopenedDescription: 'Send notifications when issues are reopened.', + userissuereopenedDescription: + 'Get notified when issues you reported are reopened.', + adminissuereopenedDescription: + 'Get notified when issues are reopened by other users.', }); export const hasNotificationType = ( @@ -74,6 +96,10 @@ export enum Notification { TEST_NOTIFICATION = 32, MEDIA_DECLINED = 64, MEDIA_AUTO_APPROVED = 128, + ISSUE_CREATED = 256, + ISSUE_COMMENT = 512, + ISSUE_RESOLVED = 1024, + ISSUE_REOPENED = 2048, } export const ALL_NOTIFICATIONS = Object.values(Notification) @@ -85,7 +111,7 @@ export interface NotificationItem { name: string; description: string; value: Notification; - hasNotifyUser?: boolean; + hasNotifyUser: boolean; children?: NotificationItem[]; hidden?: boolean; } @@ -173,6 +199,7 @@ const NotificationTypeSelector: React.FC = ({ : messages.mediarequestedDescription ), value: Notification.MEDIA_PENDING, + hasNotifyUser: false, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), }, { @@ -184,6 +211,7 @@ const NotificationTypeSelector: React.FC = ({ : messages.mediaAutoApprovedDescription ), value: Notification.MEDIA_AUTO_APPROVED, + hasNotifyUser: false, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), }, { @@ -231,6 +259,76 @@ const NotificationTypeSelector: React.FC = ({ ), value: Notification.MEDIA_FAILED, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), + hasNotifyUser: false, + }, + { + id: 'issue-created', + name: intl.formatMessage(messages.issuecreated), + description: intl.formatMessage( + user + ? messages.userissuecreatedDescription + : messages.issuecreatedDescription + ), + value: Notification.ISSUE_CREATED, + hidden: user && !hasPermission(Permission.MANAGE_ISSUES), + hasNotifyUser: false, + }, + { + id: 'issue-comment', + name: intl.formatMessage(messages.issuecomment), + description: intl.formatMessage( + user + ? hasPermission(Permission.MANAGE_ISSUES) + ? messages.adminissuecommentDescription + : messages.userissuecommentDescription + : messages.issuecommentDescription + ), + value: Notification.ISSUE_COMMENT, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + hasNotifyUser: + !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, + }, + { + id: 'issue-resolved', + name: intl.formatMessage(messages.issueresolved), + description: intl.formatMessage( + user + ? hasPermission(Permission.MANAGE_ISSUES) + ? messages.adminissueresolvedDescription + : messages.userissueresolvedDescription + : messages.issueresolvedDescription + ), + value: Notification.ISSUE_RESOLVED, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + hasNotifyUser: + !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, + }, + { + id: 'issue-reopened', + name: intl.formatMessage(messages.issuereopened), + description: intl.formatMessage( + user + ? hasPermission(Permission.MANAGE_ISSUES) + ? messages.adminissuereopenedDescription + : messages.userissuereopenedDescription + : messages.issuereopenedDescription + ), + value: Notification.ISSUE_REOPENED, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + hasNotifyUser: + !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, }, ]; diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index 71c6fc8b5..504e615db 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -9,21 +9,24 @@ export const messages = defineMessages({ 'Full administrator access. Bypasses all other permission checks.', users: 'Manage Users', usersDescription: - 'Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.', + 'Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.', settings: 'Manage Settings', settingsDescription: - 'Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.', + 'Grant permission to modify global settings. A user must have this permission to grant it to others.', managerequests: 'Manage Requests', managerequestsDescription: - 'Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.', + 'Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.', request: 'Request', - requestDescription: 'Grant permission to request non-4K media.', + requestDescription: 'Grant permission to submit requests for non-4K media.', requestMovies: 'Request Movies', - requestMoviesDescription: 'Grant permission to request non-4K movies.', + requestMoviesDescription: + 'Grant permission to submit requests for non-4K movies.', requestTv: 'Request Series', - requestTvDescription: 'Grant permission to request non-4K series.', + requestTvDescription: + 'Grant permission to submit requests for non-4K series.', autoapprove: 'Auto-Approve', - autoapproveDescription: 'Grant automatic approval for all non-4K requests.', + autoapproveDescription: + 'Grant automatic approval for all non-4K media requests.', autoapproveMovies: 'Auto-Approve Movies', autoapproveMoviesDescription: 'Grant automatic approval for non-4K movie requests.', @@ -31,7 +34,8 @@ export const messages = defineMessages({ autoapproveSeriesDescription: 'Grant automatic approval for non-4K series requests.', autoapprove4k: 'Auto-Approve 4K', - autoapprove4kDescription: 'Grant automatic approval for all 4K requests.', + autoapprove4kDescription: + 'Grant automatic approval for all 4K media requests.', autoapprove4kMovies: 'Auto-Approve 4K Movies', autoapprove4kMoviesDescription: 'Grant automatic approval for 4K movie requests.', @@ -39,16 +43,25 @@ export const messages = defineMessages({ autoapprove4kSeriesDescription: 'Grant automatic approval for 4K series requests.', request4k: 'Request 4K', - request4kDescription: 'Grant permission to request 4K media.', + request4kDescription: 'Grant permission to submit requests for 4K media.', request4kMovies: 'Request 4K Movies', - request4kMoviesDescription: 'Grant permission to request 4K movies.', + request4kMoviesDescription: + 'Grant permission to submit requests for 4K movies.', request4kTv: 'Request 4K Series', - request4kTvDescription: 'Grant permission to request 4K series.', + request4kTvDescription: 'Grant permission to submit requests for 4K series.', advancedrequest: 'Advanced Requests', advancedrequestDescription: - 'Grant permission to use advanced request options.', + 'Grant permission to modify advanced media request options.', viewrequests: 'View Requests', - viewrequestsDescription: "Grant permission to view other users' requests.", + viewrequestsDescription: + 'Grant permission to view media requests submitted by other users.', + manageissues: 'Manage Issues', + manageissuesDescription: 'Grant permission to manage media issues.', + createissues: 'Report Issues', + createissuesDescription: 'Grant permission to report media issues.', + viewissues: 'View Issues', + viewissuesDescription: + 'Grant permission to view media issues reported by other users.', }); interface PermissionEditProps { @@ -223,6 +236,26 @@ export const PermissionEdit: React.FC = ({ }, ], }, + { + id: 'manageissues', + name: intl.formatMessage(messages.manageissues), + description: intl.formatMessage(messages.manageissuesDescription), + permission: Permission.MANAGE_ISSUES, + children: [ + { + id: 'createissues', + name: intl.formatMessage(messages.createissues), + description: intl.formatMessage(messages.createissuesDescription), + permission: Permission.CREATE_ISSUES, + }, + { + id: 'viewissues', + name: intl.formatMessage(messages.viewissues), + description: intl.formatMessage(messages.viewissuesDescription), + permission: Permission.VIEW_ISSUES, + }, + ], + }, ]; return ( diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 08cc9ec69..98f415207 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -233,7 +233,7 @@ const RequestCard: React.FC = ({ request, onTitleData }) => { alt="" className="avatar-sm" /> - + {requestData.requestedBy.displayName} diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index a3a203e7f..35b07a8ac 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -63,7 +63,7 @@ const RequestItemError: React.FC = ({ }; return ( -
+
{intl.formatMessage(messages.mediaerror)} @@ -104,7 +104,7 @@ const RequestItem: React.FC = ({ ? `/api/v1/movie/${request.media.tmdbId}` : `/api/v1/tv/${request.media.tmdbId}`; const { data: title, error } = useSWR( - inView ? `${url}` : null + inView ? url : null ); const { data: requestData, @@ -149,7 +149,7 @@ const RequestItem: React.FC = ({ if (!title && !error) { return (
); @@ -178,7 +178,7 @@ const RequestItem: React.FC = ({ setShowEditModal(false); }} /> -
+
{title.backdropPath && (
= ({ : `/tv/${requestData.media.tmdbId}` } > - + = ({ alt="" className="ml-1.5 avatar-sm" /> - + {requestData.requestedBy.displayName} @@ -393,7 +393,7 @@ const RequestItem: React.FC = ({ alt="" className="ml-1.5 avatar-sm" /> - + {requestData.modifiedBy.displayName} diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index c74382282..7c1aa1545 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -22,7 +22,7 @@ import RequestItem from './RequestItem'; const messages = defineMessages({ requests: 'Requests', showallrequests: 'Show All Requests', - sortAdded: 'Request Date', + sortAdded: 'Most Recent', sortModified: 'Last Modified', }); diff --git a/src/components/RequestModal/AdvancedRequester/index.tsx b/src/components/RequestModal/AdvancedRequester/index.tsx index 9fe8bdafd..ef52e547a 100644 --- a/src/components/RequestModal/AdvancedRequester/index.tsx +++ b/src/components/RequestModal/AdvancedRequester/index.tsx @@ -267,13 +267,23 @@ const AdvancedRequester: React.FC = ({ ); } - if ((!data || selectedServer === null) && !selectedUser) { + if ( + (!data || + selectedServer === null || + (data.filter((server) => server.is4k === is4k).length < 2 && + (!serverData || + (serverData.profiles.length < 2 && + serverData.rootFolders.length < 2 && + (serverData.languageProfiles ?? []).length < 2 && + !serverData.tags?.length)))) && + (!selectedUser || (userData?.results ?? []).length < 2) + ) { return null; } return ( <> -
+
{intl.formatMessage(messages.advancedoptions)}
@@ -503,7 +513,8 @@ const AdvancedRequester: React.FC = ({
)} {hasPermission([Permission.MANAGE_REQUESTS, Permission.MANAGE_USERS]) && - selectedUser && ( + selectedUser && + (userData?.results ?? []).length > 1 && ( {title} requested successfully!', + requesttitle: 'Request {title}', + request4ktitle: 'Request {title} in 4K', + requesterror: 'Something went wrong while submitting the request.', + selectmovies: 'Select Movie(s)', + requestmovies: 'Request {count} {count, plural, one {Movie} other {Movies}}', + requestmovies4k: + 'Request {count} {count, plural, one {Movie} other {Movies}} in 4K', +}); + +interface RequestModalProps extends React.HTMLAttributes { + tmdbId: number; + is4k?: boolean; + onCancel?: () => void; + onComplete?: (newStatus: MediaStatus) => void; + onUpdating?: (isUpdating: boolean) => void; +} + +const CollectionRequestModal: React.FC = ({ + onCancel, + onComplete, + tmdbId, + onUpdating, + is4k = false, +}) => { + const [isUpdating, setIsUpdating] = useState(false); + const [requestOverrides, setRequestOverrides] = + useState(null); + const [selectedParts, setSelectedParts] = useState([]); + const { addToast } = useToasts(); + const { data, error } = useSWR(`/api/v1/collection/${tmdbId}`, { + revalidateOnMount: true, + }); + const intl = useIntl(); + const { user, hasPermission } = useUser(); + const { data: quota } = useSWR( + user && + (!requestOverrides?.user?.id || hasPermission(Permission.MANAGE_USERS)) + ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` + : null + ); + + const currentlyRemaining = + (quota?.movie.remaining ?? 0) - selectedParts.length; + + const getAllParts = (): number[] => { + return (data?.parts ?? []).map((part) => part.id); + }; + + const getAllRequestedParts = (): number[] => { + const requestedParts = (data?.parts ?? []).reduce( + (requestedParts, part) => { + return [ + ...requestedParts, + ...(part.mediaInfo?.requests ?? []) + .filter( + (request) => + request.is4k === is4k && + request.status !== MediaRequestStatus.DECLINED + ) + .map((part) => part.id), + ]; + }, + [] as number[] + ); + + const availableParts = (data?.parts ?? []) + .filter( + (part) => + part.mediaInfo && + (part.mediaInfo[is4k ? 'status4k' : 'status'] === + MediaStatus.AVAILABLE || + part.mediaInfo[is4k ? 'status4k' : 'status'] === + MediaStatus.PROCESSING) && + !requestedParts.includes(part.id) + ) + .map((part) => part.id); + + return [...requestedParts, ...availableParts]; + }; + + const isSelectedPart = (tmdbId: number): boolean => + selectedParts.includes(tmdbId); + + const togglePart = (tmdbId: number): void => { + // If this part already has a pending request, don't allow it to be toggled + if (getAllRequestedParts().includes(tmdbId)) { + return; + } + + // If there are no more remaining requests available, block toggle + if ( + quota?.movie.limit && + currentlyRemaining <= 0 && + !isSelectedPart(tmdbId) + ) { + return; + } + + if (selectedParts.includes(tmdbId)) { + setSelectedParts((parts) => parts.filter((partId) => partId !== tmdbId)); + } else { + setSelectedParts((parts) => [...parts, tmdbId]); + } + }; + + const unrequestedParts = getAllParts().filter( + (tmdbId) => !getAllRequestedParts().includes(tmdbId) + ); + + const toggleAllParts = (): void => { + // If the user has a quota and not enough requests for all parts, block toggleAllParts + if ( + quota?.movie.limit && + (quota?.movie.remaining ?? 0) < unrequestedParts.length + ) { + return; + } + + if ( + data && + selectedParts.length >= 0 && + selectedParts.length < unrequestedParts.length + ) { + setSelectedParts(unrequestedParts); + } else { + setSelectedParts([]); + } + }; + + const isAllParts = (): boolean => { + if (!data) { + return false; + } + + return ( + selectedParts.length === + getAllParts().filter((part) => !getAllRequestedParts().includes(part)) + .length + ); + }; + + const getPartRequest = (tmdbId: number): MediaRequest | undefined => { + const part = (data?.parts ?? []).find((part) => part.id === tmdbId); + + return (part?.mediaInfo?.requests ?? []).find( + (request) => + request.is4k === is4k && request.status !== MediaRequestStatus.DECLINED + ); + }; + + useEffect(() => { + if (onUpdating) { + onUpdating(isUpdating); + } + }, [isUpdating, onUpdating]); + + const sendRequest = useCallback(async () => { + setIsUpdating(true); + + try { + let overrideParams = {}; + if (requestOverrides) { + overrideParams = { + serverId: requestOverrides.server, + profileId: requestOverrides.profile, + rootFolder: requestOverrides.folder, + userId: requestOverrides.user?.id, + tags: requestOverrides.tags, + }; + } + + await Promise.all( + ( + data?.parts.filter((part) => selectedParts.includes(part.id)) ?? [] + ).map(async (part) => { + await axios.post('/api/v1/request', { + mediaId: part.id, + mediaType: 'movie', + is4k, + ...overrideParams, + }); + }) + ); + + if (onComplete) { + onComplete( + selectedParts.length === (data?.parts ?? []).length + ? MediaStatus.UNKNOWN + : MediaStatus.PARTIALLY_AVAILABLE + ); + } + + addToast( + + {intl.formatMessage(messages.requestSuccess, { + title: data?.name, + strong: function strong(msg) { + return {msg}; + }, + })} + , + { appearance: 'success', autoDismiss: true } + ); + } catch (e) { + addToast(intl.formatMessage(messages.requesterror), { + appearance: 'error', + autoDismiss: true, + }); + } finally { + setIsUpdating(false); + } + }, [requestOverrides, data, onComplete, addToast, intl, selectedParts, is4k]); + + const hasAutoApprove = hasPermission( + [ + Permission.MANAGE_REQUESTS, + is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE, + is4k ? Permission.AUTO_APPROVE_4K_MOVIE : Permission.AUTO_APPROVE_MOVIE, + ], + { type: 'or' } + ); + + return ( + } + backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} + > + {hasAutoApprove && !quota?.movie.restricted && ( +
+ +
+ )} + {(quota?.movie.limit ?? 0) > 0 && ( + + )} +
+
+
+
+ + + + + + + + + + {data?.parts.map((part) => { + const partRequest = getPartRequest(part.id); + const partMedia = + part.mediaInfo && + part.mediaInfo[is4k ? 'status4k' : 'status'] !== + MediaStatus.UNKNOWN + ? part.mediaInfo + : undefined; + + return ( + + + + + + ); + })} + +
+ toggleAllParts()} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Space') { + toggleAllParts(); + } + }} + className={`relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 pt-2 cursor-pointer focus:outline-none ${ + quota?.movie.limit && + (quota.movie.remaining ?? 0) < unrequestedParts.length + ? 'opacity-50' + : '' + }`} + > + + + + + {intl.formatMessage(globalMessages.movie)} + + {intl.formatMessage(globalMessages.status)} +
+ togglePart(part.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Space') { + togglePart(part.id); + } + }} + className={`pt-2 relative inline-flex items-center justify-center flex-shrink-0 h-5 w-10 cursor-pointer focus:outline-none ${ + !!partMedia || + partRequest || + (quota?.movie.limit && + currentlyRemaining <= 0 && + !isSelectedPart(part.id)) + ? 'opacity-50' + : '' + }`} + > + + + + +
+ +
+
+
+ {part.releaseDate?.slice(0, 4)} +
+
+ {part.title} +
+
+
+ {!partMedia && !partRequest && ( + + {intl.formatMessage(globalMessages.notrequested)} + + )} + {!partMedia && + partRequest?.status === + MediaRequestStatus.PENDING && ( + + {intl.formatMessage(globalMessages.pending)} + + )} + {((!partMedia && + partRequest?.status === + MediaRequestStatus.APPROVED) || + partMedia?.[is4k ? 'status4k' : 'status'] === + MediaStatus.PROCESSING) && ( + + {intl.formatMessage(globalMessages.requested)} + + )} + {partMedia?.[is4k ? 'status4k' : 'status'] === + MediaStatus.AVAILABLE && ( + + {intl.formatMessage(globalMessages.available)} + + )} +
+
+
+
+
+ {(hasPermission(Permission.REQUEST_ADVANCED) || + hasPermission(Permission.MANAGE_REQUESTS)) && ( + { + setRequestOverrides(overrides); + }} + /> + )} +
+ ); +}; + +export default CollectionRequestModal; diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index ccfa4f1f9..648f9aff0 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -23,12 +23,14 @@ const messages = defineMessages({ requesttitle: 'Request {title}', request4ktitle: 'Request {title} in 4K', edit: 'Edit Request', + approve: 'Approve Request', cancel: 'Cancel Request', pendingrequest: 'Pending Request for {title}', pending4krequest: 'Pending 4K Request for {title}', requestfrom: "{username}'s request is pending approval.", errorediting: 'Something went wrong while editing the request.', requestedited: 'Request for {title} edited successfully!', + requestApproved: 'Request for {title} approved!', requesterror: 'Something went wrong while submitting the request.', pendingapproval: 'Your request is pending approval.', }); @@ -60,7 +62,10 @@ const MovieRequestModal: React.FC = ({ const intl = useIntl(); const { user, hasPermission } = useUser(); const { data: quota } = useSWR( - user ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` : null + user && + (!requestOverrides?.user?.id || hasPermission(Permission.MANAGE_USERS)) + ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` + : null ); useEffect(() => { @@ -156,7 +161,7 @@ const MovieRequestModal: React.FC = ({ } }; - const updateRequest = async () => { + const updateRequest = async (alsoApproveRequest = false) => { setIsUpdating(true); try { @@ -169,14 +174,23 @@ const MovieRequestModal: React.FC = ({ tags: requestOverrides?.tags, }); + if (alsoApproveRequest) { + await axios.post(`/api/v1/request/${editRequest?.id}/approve`); + } + addToast( - {intl.formatMessage(messages.requestedited, { - title: data?.title, - strong: function strong(msg) { - return {msg}; - }, - })} + {intl.formatMessage( + alsoApproveRequest + ? messages.requestApproved + : messages.requestedited, + { + title: data?.title, + strong: function strong(msg) { + return {msg}; + }, + } + )} , { appearance: 'success', @@ -199,12 +213,6 @@ const MovieRequestModal: React.FC = ({ if (editRequest) { const isOwner = editRequest.requestedBy.id === user?.id; - const showEditButton = hasPermission( - [Permission.MANAGE_REQUESTS, Permission.REQUEST_ADVANCED], - { - type: 'or', - } - ); return ( = ({ is4k ? messages.pending4krequest : messages.pendingrequest, { title: data?.title } )} - onOk={() => (showEditButton ? updateRequest() : cancelRequest())} + onOk={() => + hasPermission(Permission.MANAGE_REQUESTS) + ? updateRequest(true) + : hasPermission(Permission.REQUEST_ADVANCED) + ? updateRequest() + : cancelRequest() + } okDisabled={isUpdating} okText={ - showEditButton + hasPermission(Permission.MANAGE_REQUESTS) + ? intl.formatMessage(messages.approve) + : hasPermission(Permission.REQUEST_ADVANCED) ? intl.formatMessage(messages.edit) : intl.formatMessage(messages.cancel) } - okButtonType={showEditButton ? 'primary' : 'danger'} + okButtonType={ + hasPermission(Permission.MANAGE_REQUESTS) + ? 'success' + : hasPermission(Permission.REQUEST_ADVANCED) + ? 'primary' + : 'danger' + } onSecondary={ - isOwner && showEditButton ? () => cancelRequest() : undefined + isOwner && + hasPermission( + [Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS], + { type: 'or' } + ) + ? () => cancelRequest() + : undefined } secondaryDisabled={isUpdating} secondaryText={ - isOwner && showEditButton + isOwner && + hasPermission( + [Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS], + { type: 'or' } + ) ? intl.formatMessage(messages.cancel) : undefined } @@ -244,22 +276,20 @@ const MovieRequestModal: React.FC = ({ })} {(hasPermission(Permission.REQUEST_ADVANCED) || hasPermission(Permission.MANAGE_REQUESTS)) && ( -
- { - setRequestOverrides(overrides); - }} - /> -
+ { + setRequestOverrides(overrides); + }} + /> )}
); diff --git a/src/components/RequestModal/QuotaDisplay/index.tsx b/src/components/RequestModal/QuotaDisplay/index.tsx index a044f954f..0b73c1b85 100644 --- a/src/components/RequestModal/QuotaDisplay/index.tsx +++ b/src/components/RequestModal/QuotaDisplay/index.tsx @@ -99,8 +99,8 @@ const QuotaDisplay: React.FC = ({
{intl.formatMessage( userOverride - ? messages.requiredquota - : messages.requiredquotaUser, + ? messages.requiredquotaUser + : messages.requiredquota, { seasons: overLimit, strong: function strong(msg) { diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 1d6a9e640..9a879b486 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -30,13 +30,15 @@ const messages = defineMessages({ requesttitle: 'Request {title}', request4ktitle: 'Request {title} in 4K', edit: 'Edit Request', + approve: 'Approve Request', cancel: 'Cancel Request', pendingrequest: 'Pending Request for {title}', pending4krequest: 'Pending 4K Request for {title}', requestfrom: "{username}'s request is pending approval.", requestseasons: 'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}', - requestall: 'Request All Seasons', + requestseasons4k: + 'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}} in 4K', alreadyrequested: 'Already Requested', selectseason: 'Select Season(s)', season: 'Season', @@ -45,6 +47,7 @@ const messages = defineMessages({ extras: 'Extras', errorediting: 'Something went wrong while editing the request.', requestedited: 'Request for {title} edited successfully!', + requestApproved: 'Request for {title} approved!', requestcancelled: 'Request for {title} canceled.', autoapproval: 'Automatic Approval', requesterror: 'Something went wrong while submitting the request.', @@ -88,7 +91,10 @@ const TvRequestModal: React.FC = ({ }); const [tvdbId, setTvdbId] = useState(undefined); const { data: quota } = useSWR( - user ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` : null + user && + (!requestOverrides?.user?.id || hasPermission(Permission.MANAGE_USERS)) + ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` + : null ); const currentlyRemaining = @@ -96,7 +102,7 @@ const TvRequestModal: React.FC = ({ selectedSeasons.length + (editRequest?.seasons ?? []).length; - const updateRequest = async () => { + const updateRequest = async (alsoApproveRequest = false) => { if (!editRequest) { return; } @@ -117,6 +123,10 @@ const TvRequestModal: React.FC = ({ tags: requestOverrides?.tags, seasons: selectedSeasons, }); + + if (alsoApproveRequest) { + await axios.post(`/api/v1/request/${editRequest.id}/approve`); + } } else { await axios.delete(`/api/v1/request/${editRequest.id}`); } @@ -124,12 +134,17 @@ const TvRequestModal: React.FC = ({ addToast( {selectedSeasons.length > 0 - ? intl.formatMessage(messages.requestedited, { - title: data?.name, - strong: function strong(msg) { - return {msg}; - }, - }) + ? intl.formatMessage( + alsoApproveRequest + ? messages.requestApproved + : messages.requestedited, + { + title: data?.name, + strong: function strong(msg) { + return {msg}; + }, + } + ) : intl.formatMessage(messages.requestcancelled, { title: data?.name, strong: function strong(msg) { @@ -368,7 +383,13 @@ const TvRequestModal: React.FC = ({ loading={!data && !error} backgroundClickable onCancel={tvdbId ? () => setSearchModal({ show: true }) : onCancel} - onOk={() => (editRequest ? updateRequest() : sendRequest())} + onOk={() => + editRequest + ? hasPermission(Permission.MANAGE_REQUESTS) + ? updateRequest(true) + : updateRequest() + : sendRequest() + } title={intl.formatMessage( editRequest ? is4k @@ -383,16 +404,23 @@ const TvRequestModal: React.FC = ({ editRequest ? selectedSeasons.length === 0 ? intl.formatMessage(messages.cancel) + : hasPermission(Permission.MANAGE_REQUESTS) + ? intl.formatMessage(messages.approve) : intl.formatMessage(messages.edit) : getAllRequestedSeasons().length >= getAllSeasons().length ? intl.formatMessage(messages.alreadyrequested) : !settings.currentSettings.partialRequestsEnabled - ? intl.formatMessage(messages.requestall) + ? intl.formatMessage( + is4k ? globalMessages.request4k : globalMessages.request + ) : selectedSeasons.length === 0 ? intl.formatMessage(messages.selectseason) - : intl.formatMessage(messages.requestseasons, { - seasonCount: selectedSeasons.length, - }) + : intl.formatMessage( + is4k ? messages.requestseasons4k : messages.requestseasons, + { + seasonCount: selectedSeasons.length, + } + ) } okDisabled={ editRequest @@ -406,11 +434,14 @@ const TvRequestModal: React.FC = ({ selectedSeasons.length === 0) } okButtonType={ - editRequest && - settings.currentSettings.partialRequestsEnabled && - selectedSeasons.length === 0 - ? 'danger' - : `primary` + editRequest + ? settings.currentSettings.partialRequestsEnabled && + selectedSeasons.length === 0 + ? 'danger' + : hasPermission(Permission.MANAGE_REQUESTS) + ? 'success' + : 'primary' + : 'primary' } cancelText={ editRequest @@ -440,7 +471,7 @@ const TvRequestModal: React.FC = ({ !( quota?.tv.limit && !settings.currentSettings.partialRequestsEnabled && - unrequestedSeasons.length > (quota?.tv.limit ?? 0) + unrequestedSeasons.length > (quota?.tv.remaining ?? 0) ) && getAllRequestedSeasons().length < getAllSeasons().length && !editRequest && ( @@ -457,7 +488,7 @@ const TvRequestModal: React.FC = ({ quota={quota?.tv} remaining={ !settings.currentSettings.partialRequestsEnabled && - unrequestedSeasons.length > (quota?.tv.limit ?? 0) + unrequestedSeasons.length > (quota?.tv.remaining ?? 0) ? 0 : currentlyRemaining } @@ -468,7 +499,7 @@ const TvRequestModal: React.FC = ({ } overLimit={ !settings.currentSettings.partialRequestsEnabled && - unrequestedSeasons.length > (quota?.tv.limit ?? 0) + unrequestedSeasons.length > (quota?.tv.remaining ?? 0) ? unrequestedSeasons.length : undefined } @@ -667,28 +698,26 @@ const TvRequestModal: React.FC = ({
{(hasPermission(Permission.REQUEST_ADVANCED) || hasPermission(Permission.MANAGE_REQUESTS)) && ( -
- keyword.id === ANIME_KEYWORD_ID - )} - onChange={(overrides) => setRequestOverrides(overrides)} - requestUser={editRequest?.requestedBy} - defaultOverrides={ - editRequest - ? { - folder: editRequest.rootFolder, - profile: editRequest.profileId, - server: editRequest.serverId, - language: editRequest.languageProfileId, - tags: editRequest.tags, - } - : undefined - } - /> -
+ keyword.id === ANIME_KEYWORD_ID + )} + onChange={(overrides) => setRequestOverrides(overrides)} + requestUser={editRequest?.requestedBy} + defaultOverrides={ + editRequest + ? { + folder: editRequest.rootFolder, + profile: editRequest.profileId, + server: editRequest.serverId, + language: editRequest.languageProfileId, + tags: editRequest.tags, + } + : undefined + } + /> )} ); diff --git a/src/components/RequestModal/index.tsx b/src/components/RequestModal/index.tsx index 7ba09bdef..dfbb715e6 100644 --- a/src/components/RequestModal/index.tsx +++ b/src/components/RequestModal/index.tsx @@ -1,13 +1,14 @@ import React from 'react'; -import MovieRequestModal from './MovieRequestModal'; import type { MediaStatus } from '../../../server/constants/media'; -import TvRequestModal from './TvRequestModal'; -import Transition from '../Transition'; import { MediaRequest } from '../../../server/entity/MediaRequest'; +import Transition from '../Transition'; +import CollectionRequestModal from './CollectionRequestModal'; +import MovieRequestModal from './MovieRequestModal'; +import TvRequestModal from './TvRequestModal'; interface RequestModalProps { show: boolean; - type: 'movie' | 'tv'; + type: 'movie' | 'tv' | 'collection'; tmdbId: number; is4k?: boolean; editRequest?: MediaRequest; @@ -26,29 +27,6 @@ const RequestModal: React.FC = ({ onUpdating, onCancel, }) => { - if (type === 'tv') { - return ( - - - - ); - } - return ( = ({ leaveTo="opacity-0" show={show} > - + {type === 'movie' ? ( + + ) : type === 'tv' ? ( + + ) : ( + + )} ); }; diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index 67cfa031a..9e2a67019 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -6,6 +6,7 @@ import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import * as Yup from 'yup'; +import useSettings from '../../../hooks/useSettings'; import globalMessages from '../../../i18n/globalMessages'; import Button from '../../Common/Button'; import LoadingSpinner from '../../Common/LoadingSpinner'; @@ -29,6 +30,7 @@ const messages = defineMessages({ const NotificationsDiscord: React.FC = () => { const intl = useIntl(); + const settings = useSettings(); const { addToast, removeToast } = useToasts(); const [isTesting, setIsTesting] = useState(false); const { data, error, revalidate } = useSWR( @@ -195,7 +197,12 @@ const NotificationsDiscord: React.FC = () => {
- +
{errors.botUsername && touched.botUsername && (
{errors.botUsername}
diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index e14f2f190..ad501c692 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -82,7 +82,7 @@ const NotificationsEmail: React.FC = () => { otherwise: Yup.string().nullable(), }) .matches( - /^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, + /^(((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])):((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))@)?(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, intl.formatMessage(messages.validationSmtpHostRequired) ), smtpPort: Yup.number().when('enabled', { diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index bcb03df89..d76fdde33 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -51,8 +51,8 @@ const NotificationsTelegram: React.FC = () => { otherwise: Yup.string().nullable(), }), chatId: Yup.string() - .when('enabled', { - is: true, + .when(['enabled', 'types'], { + is: (enabled: boolean, types: number) => enabled && !!types, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationChatIdRequired)), diff --git a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx index 36e0a3c0d..e36e44643 100644 --- a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx +++ b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx @@ -18,27 +18,38 @@ const JSONEditor = dynamic(() => import('../../../JSONEditor'), { ssr: false }); const defaultPayload = { notification_type: '{{notification_type}}', + event: '{{event}}', subject: '{{subject}}', message: '{{message}}', image: '{{image}}', - email: '{{notifyuser_email}}', - username: '{{notifyuser_username}}', - avatar: '{{notifyuser_avatar}}', '{{media}}': { media_type: '{{media_type}}', tmdbId: '{{media_tmdbid}}', - imdbId: '{{media_imdbid}}', tvdbId: '{{media_tvdbid}}', status: '{{media_status}}', status4k: '{{media_status4k}}', }, - '{{extra}}': [], '{{request}}': { request_id: '{{request_id}}', requestedBy_email: '{{requestedBy_email}}', requestedBy_username: '{{requestedBy_username}}', requestedBy_avatar: '{{requestedBy_avatar}}', }, + '{{issue}}': { + issue_id: '{{issue_id}}', + issue_type: '{{issue_type}}', + issue_status: '{{issue_status}}', + reportedBy_email: '{{reportedBy_email}}', + reportedBy_username: '{{reportedBy_username}}', + reportedBy_avatar: '{{reportedBy_avatar}}', + }, + '{{comment}}': { + comment_message: '{{comment_message}}', + commentedBy_email: '{{commentedBy_email}}', + commentedBy_username: '{{commentedBy_username}}', + commentedBy_avatar: '{{commentedBy_avatar}}', + }, + '{{extra}}': [], }; const messages = defineMessages({ diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx index 1c3e6904c..63fe4fed7 100644 --- a/src/components/Settings/RadarrModal/index.tsx +++ b/src/components/Settings/RadarrModal/index.tsx @@ -26,7 +26,7 @@ const messages = defineMessages({ editradarr: 'Edit Radarr Server', edit4kradarr: 'Edit 4K Radarr Server', validationNameRequired: 'You must provide a server name', - validationHostnameRequired: 'You must provide a hostname or IP address', + validationHostnameRequired: 'You must provide a valid hostname or IP address', validationPortRequired: 'You must provide a valid port number', validationApiKeyRequired: 'You must provide an API key', validationRootFolderRequired: 'You must select a root folder', @@ -113,7 +113,7 @@ const RadarrModal: React.FC = ({ hostname: Yup.string() .required(intl.formatMessage(messages.validationHostnameRequired)) .matches( - /^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, + /^(((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])):((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))@)?(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, intl.formatMessage(messages.validationHostnameRequired) ), port: Yup.number() diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 572246fde..a9e8c0cf6 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -321,7 +321,9 @@ const SettingsLogs: React.FC = () => { {row.level.toUpperCase()} - {row.label} + + {row.label ?? ''} + {row.message} {row.data && ( diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx index d790d6de1..1c77c3031 100644 --- a/src/components/Settings/SettingsPlex.tsx +++ b/src/components/Settings/SettingsPlex.tsx @@ -112,7 +112,7 @@ const SettingsPlex: React.FC = ({ onComplete }) => { .nullable() .required(intl.formatMessage(messages.validationHostnameRequired)) .matches( - /^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, + /^(((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])):((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))@)?(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, intl.formatMessage(messages.validationHostnameRequired) ), port: Yup.number() diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index 862d3cbe8..ad287e16e 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -26,7 +26,7 @@ const messages = defineMessages({ editsonarr: 'Edit Sonarr Server', edit4ksonarr: 'Edit 4K Sonarr Server', validationNameRequired: 'You must provide a server name', - validationHostnameRequired: 'You must provide a hostname or IP address', + validationHostnameRequired: 'You must provide a valid hostname or IP address', validationPortRequired: 'You must provide a valid port number', validationApiKeyRequired: 'You must provide an API key', validationRootFolderRequired: 'You must select a root folder', @@ -124,7 +124,7 @@ const SonarrModal: React.FC = ({ hostname: Yup.string() .required(intl.formatMessage(messages.validationHostnameRequired)) .matches( - /^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, + /^(((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])):((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))@)?(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, intl.formatMessage(messages.validationHostnameRequired) ), port: Yup.number() diff --git a/src/components/Setup/index.tsx b/src/components/Setup/index.tsx index 9d6642299..c4a754dc4 100644 --- a/src/components/Setup/index.tsx +++ b/src/components/Setup/index.tsx @@ -2,7 +2,7 @@ import axios from 'axios'; import { useRouter } from 'next/router'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { mutate } from 'swr'; +import useSWR, { mutate } from 'swr'; import useLocale from '../../hooks/useLocale'; import AppDataWarning from '../AppDataWarning'; import Badge from '../Common/Badge'; @@ -51,18 +51,21 @@ const Setup: React.FC = () => { } }; + const { data: backdrops } = useSWR('/api/v1/backdrops', { + refreshInterval: 0, + refreshWhenHidden: false, + revalidateOnFocus: false, + }); + return (
`https://www.themoviedb.org/t/p/original${backdrop}` + ) ?? [] + } />
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 6ccc60499..4de23164b 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -1,15 +1,12 @@ import { ArrowCircleRightIcon, CogIcon, + ExclamationIcon, FilmIcon, PlayIcon, } from '@heroicons/react/outline'; -import { - CheckCircleIcon, - DocumentRemoveIcon, - ExternalLinkIcon, -} from '@heroicons/react/solid'; -import axios from 'axios'; +import { hasFlag } from 'country-flag-icons'; +import 'country-flag-icons/3x2/flags.css'; import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useMemo, useState } from 'react'; @@ -17,6 +14,7 @@ import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import type { RTRating } from '../../../server/api/rottentomatoes'; import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb/constants'; +import { IssueStatus } from '../../../server/constants/issue'; import { MediaStatus } from '../../../server/constants/media'; import { Crew } from '../../../server/models/common'; import { TvDetails as TvDetailsType } from '../../../server/models/Tv'; @@ -33,16 +31,14 @@ import Error from '../../pages/_error'; import { sortCrewPriority } from '../../utils/creditHelpers'; import Button from '../Common/Button'; import CachedImage from '../Common/CachedImage'; -import ConfirmButton from '../Common/ConfirmButton'; import LoadingSpinner from '../Common/LoadingSpinner'; import PageTitle from '../Common/PageTitle'; import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; -import SlideOver from '../Common/SlideOver'; -import DownloadBlock from '../DownloadBlock'; import ExternalLinkBlock from '../ExternalLinkBlock'; +import IssueModal from '../IssueModal'; +import ManageSlideOver from '../ManageSlideOver'; import MediaSlider from '../MediaSlider'; import PersonCard from '../PersonCard'; -import RequestBlock from '../RequestBlock'; import RequestButton from '../RequestButton'; import RequestModal from '../RequestModal'; import Slider from '../Slider'; @@ -58,29 +54,19 @@ const messages = defineMessages({ similar: 'Similar Series', watchtrailer: 'Watch Trailer', overviewunavailable: 'Overview unavailable.', - manageModalTitle: 'Manage Series', - manageModalRequests: 'Requests', - manageModalNoRequests: 'No requests.', - manageModalClearMedia: 'Clear Media Data', - manageModalClearMediaWarning: - '* This will irreversibly remove all data for this series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.', originaltitle: 'Original Title', showtype: 'Series Type', anime: 'Anime', network: '{networkCount, plural, one {Network} other {Networks}}', viewfullcrew: 'View Full Crew', - opensonarr: 'Open Series in Sonarr', - opensonarr4k: 'Open Series in 4K Sonarr', - downloadstatus: 'Download Status', playonplex: 'Play on Plex', play4konplex: 'Play in 4K on Plex', - markavailable: 'Mark as Available', - mark4kavailable: 'Mark as Available in 4K', - allseasonsmarkedavailable: '* All seasons will be marked as available.', seasons: '{seasonCount, plural, one {# Season} other {# Seasons}}', episodeRuntime: 'Episode Runtime', episodeRuntimeMinutes: '{runtime} minutes', streamingproviders: 'Currently Streaming On', + productioncountries: + 'Production {countryCount, plural, one {Country} other {Countries}}', }); interface TvDetailsProps { @@ -95,6 +81,7 @@ const TvDetails: React.FC = ({ tv }) => { const { locale } = useLocale(); const [showRequestModal, setShowRequestModal] = useState(false); const [showManager, setShowManager] = useState(false); + const [showIssueModal, setShowIssueModal] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/tv/${router.query.tvId}`, @@ -156,20 +143,6 @@ const TvDetails: React.FC = ({ tv }) => { }); } - const deleteMedia = async () => { - if (data?.mediaInfo?.id) { - await axios.delete(`/api/v1/media/${data?.mediaInfo?.id}`); - revalidate(); - } - }; - - const markAvailable = async (is4k = false) => { - await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, { - is4k, - }); - revalidate(); - }; - const region = user?.settings?.region ? user.settings.region : settings.currentSettings.region @@ -261,6 +234,12 @@ const TvDetails: React.FC = ({ tv }) => {
)} + setShowIssueModal(false)} + show={showIssueModal} + mediaType="tv" + tmdbId={data.id} + /> = ({ tv }) => { }} onCancel={() => setShowRequestModal(false)} /> - setShowManager(false)} - subText={data.name} - > - {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || - (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( - <> -

- {intl.formatMessage(messages.downloadstatus)} -

-
-
    - {data.mediaInfo?.downloadStatus?.map((status, index) => ( -
  • - -
  • - ))} - {data.mediaInfo?.downloadStatus4k?.map((status, index) => ( -
  • - -
  • - ))} -
-
- - )} - {data?.mediaInfo && - (data.mediaInfo.status !== MediaStatus.AVAILABLE || - (data.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.series4kEnabled)) && ( -
- {data?.mediaInfo && - data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( -
- -
- )} - {data?.mediaInfo && - data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.series4kEnabled && ( -
- -
- )} -
- {intl.formatMessage(messages.allseasonsmarkedavailable)} -
-
- )} -

- {intl.formatMessage(messages.manageModalRequests)} -

-
-
    - {data.mediaInfo?.requests?.map((request) => ( -
  • - revalidate()} /> -
  • - ))} - {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • - {intl.formatMessage(messages.manageModalNoRequests)} -
  • - )} -
-
- {(data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && ( -
- {data?.mediaInfo?.serviceUrl && ( - - - - )} - {data?.mediaInfo?.serviceUrl4k && ( - - - - )} -
- )} - {data?.mediaInfo && ( -
- deleteMedia()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.manageModalClearMedia)} - -
- {intl.formatMessage(messages.manageModalClearMediaWarning)} -
-
- )} -
+ revalidate={() => revalidate()} + show={showManager} + />
= ({ tv }) => { .map((t, k) => {t}) .reduce((prev, curr) => ( <> - {prev} | {curr} + {prev} + | + {curr} ))} @@ -484,13 +334,52 @@ const TvDetails: React.FC = ({ tv }) => { isShowComplete={isComplete} is4kShowComplete={is4kComplete} /> + {(data.mediaInfo?.status === MediaStatus.AVAILABLE || + data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE || + (settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && + (data.mediaInfo?.status4k === MediaStatus.AVAILABLE || + data?.mediaInfo?.status4k === + MediaStatus.PARTIALLY_AVAILABLE))) && + hasPermission( + [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], + { + type: 'or', + } + ) && ( + + )} {hasPermission(Permission.MANAGE_REQUESTS) && ( )}
@@ -648,6 +537,37 @@ const TvDetails: React.FC = ({ tv }) => {
)} + {data.productionCountries.length > 0 && ( +
+ + {intl.formatMessage(messages.productioncountries, { + countryCount: data.productionCountries.length, + })} + + + {data.productionCountries.map((c) => { + return ( + + {hasFlag(c.iso_3166_1) && ( + + )} + + {intl.formatDisplayName(c.iso_3166_1, { + type: 'region', + fallback: 'none', + }) ?? c.name} + + + ); + })} + +
+ )} {data.networks.length > 0 && (
@@ -667,7 +587,10 @@ const TvDetails: React.FC = ({ tv }) => { )) .reduce((prev, curr) => ( <> - {prev}, {curr} + {intl.formatMessage(globalMessages.delimitedlist, { + a: prev, + b: curr, + })} ))} diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 8b7fac7a9..a544c8f99 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -46,8 +46,7 @@ const messages = defineMessages({ totalrequests: 'Requests', accounttype: 'Type', role: 'Role', - created: 'Created', - lastupdated: 'Updated', + created: 'Joined', bulkedit: 'Bulk Edit', owner: 'Owner', admin: 'Admin', @@ -75,8 +74,7 @@ const messages = defineMessages({ autogeneratepassword: 'Automatically Generate Password', autogeneratepasswordTip: 'Email a server-generated password to the user', validationEmail: 'You must provide a valid email address', - sortCreated: 'Creation Date', - sortUpdated: 'Last Updated', + sortCreated: 'Join Date', sortDisplayName: 'Display Name', sortRequests: 'Request Count', localLoginDisabled: @@ -91,7 +89,7 @@ const UserList: React.FC = () => { const settings = useSettings(); const { addToast } = useToasts(); const { user: currentUser, hasPermission: currentHasPermission } = useUser(); - const [currentSort, setCurrentSort] = useState('created'); + const [currentSort, setCurrentSort] = useState('displayname'); const [currentPageSize, setCurrentPageSize] = useState(10); const page = router.query.page ? Number(router.query.page) : 1; @@ -522,9 +520,6 @@ const UserList: React.FC = () => { - @@ -556,7 +551,6 @@ const UserList: React.FC = () => { {intl.formatMessage(messages.accounttype)} {intl.formatMessage(messages.role)} {intl.formatMessage(messages.created)} - {intl.formatMessage(messages.lastupdated)} {(data.results ?? []).length > 1 && ( + +
+
+ + ); + }} + + ); +}; + +export default UserPushbulletSettings; diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx new file mode 100644 index 000000000..88d5b9264 --- /dev/null +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx @@ -0,0 +1,229 @@ +import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import { useRouter } from 'next/router'; +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces'; +import useSettings from '../../../../hooks/useSettings'; +import { useUser } from '../../../../hooks/useUser'; +import globalMessages from '../../../../i18n/globalMessages'; +import Button from '../../../Common/Button'; +import LoadingSpinner from '../../../Common/LoadingSpinner'; +import NotificationTypeSelector from '../../../NotificationTypeSelector'; + +const messages = defineMessages({ + pushoversettingssaved: 'Pushover notification settings saved successfully!', + pushoversettingsfailed: 'Pushover notification settings failed to save.', + pushoverApplicationToken: 'Application API Token', + pushoverApplicationTokenTip: + 'Register an application for use with {applicationTitle}', + pushoverUserKey: 'User or Group Key', + pushoverUserKeyTip: + 'Your 30-character user or group identifier', + validationPushoverApplicationToken: + 'You must provide a valid application token', + validationPushoverUserKey: 'You must provide a valid user or group key', +}); + +const UserPushoverSettings: React.FC = () => { + const intl = useIntl(); + const settings = useSettings(); + const { addToast } = useToasts(); + const router = useRouter(); + const { user } = useUser({ id: Number(router.query.userId) }); + const { data, error, revalidate } = useSWR( + user ? `/api/v1/user/${user?.id}/settings/notifications` : null + ); + + const UserNotificationsPushoverSchema = Yup.object().shape({ + pushoverApplicationToken: Yup.string() + .when('types', { + is: (types: number) => !!types, + then: Yup.string() + .nullable() + .required( + intl.formatMessage(messages.validationPushoverApplicationToken) + ), + otherwise: Yup.string().nullable(), + }) + .matches( + /^[a-z\d]{30}$/i, + intl.formatMessage(messages.validationPushoverApplicationToken) + ), + pushoverUserKey: Yup.string() + .when('types', { + is: (types: number) => !!types, + then: Yup.string() + .nullable() + .required(intl.formatMessage(messages.validationPushoverUserKey)), + otherwise: Yup.string().nullable(), + }) + .matches( + /^[a-z\d]{30}$/i, + intl.formatMessage(messages.validationPushoverUserKey) + ), + }); + + if (!data && !error) { + return ; + } + + return ( + { + try { + await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { + pgpKey: data?.pgpKey, + discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: values.pushoverApplicationToken, + pushoverUserKey: values.pushoverUserKey, + telegramChatId: data?.telegramChatId, + telegramSendSilently: data?.telegramSendSilently, + notificationTypes: { + pushover: values.types, + }, + }); + addToast(intl.formatMessage(messages.pushoversettingssaved), { + appearance: 'success', + autoDismiss: true, + }); + } catch (e) { + addToast(intl.formatMessage(messages.pushoversettingsfailed), { + appearance: 'error', + autoDismiss: true, + }); + } finally { + revalidate(); + } + }} + > + {({ + errors, + touched, + isSubmitting, + isValid, + values, + setFieldValue, + setFieldTouched, + }) => { + return ( +
+
+ +
+
+ +
+ {errors.pushoverApplicationToken && + touched.pushoverApplicationToken && ( +
+ {errors.pushoverApplicationToken} +
+ )} +
+
+
+ +
+
+ +
+ {errors.pushoverUserKey && touched.pushoverUserKey && ( +
{errors.pushoverUserKey}
+ )} +
+
+ { + setFieldValue('types', newTypes); + setFieldTouched('types'); + }} + error={ + errors.types && touched.types + ? (errors.types as string) + : undefined + } + /> +
+
+ + + +
+
+ + ); + }} +
+ ); +}; + +export default UserPushoverSettings; diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx index b27e5afed..96adfdcf8 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx @@ -37,7 +37,7 @@ const UserTelegramSettings: React.FC = () => { const UserNotificationsTelegramSchema = Yup.object().shape({ telegramChatId: Yup.string() .when('types', { - is: (value: unknown) => !!value, + is: (types: number) => !!types, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationTelegramChatId)), @@ -67,6 +67,9 @@ const UserTelegramSettings: React.FC = () => { await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { pgpKey: data?.pgpKey, discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, telegramChatId: values.telegramChatId, telegramSendSilently: values.telegramSendSilently, notificationTypes: { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx index d2e36810a..6cfb46532 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx @@ -44,6 +44,9 @@ const UserWebPushSettings: React.FC = () => { await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { pgpKey: data?.pgpKey, discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, telegramChatId: data?.telegramChatId, telegramSendSilently: data?.telegramSendSilently, notificationTypes: { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx index 0f58f7e7b..6f2cc64f1 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx @@ -5,6 +5,8 @@ import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces'; import DiscordLogo from '../../../../assets/extlogos/discord.svg'; +import PushbulletLogo from '../../../../assets/extlogos/pushbullet.svg'; +import PushoverLogo from '../../../../assets/extlogos/pushover.svg'; import TelegramLogo from '../../../../assets/extlogos/telegram.svg'; import { useUser } from '../../../../hooks/useUser'; import globalMessages from '../../../../i18n/globalMessages'; @@ -64,6 +66,28 @@ const UserNotificationSettings: React.FC = ({ children }) => { route: '/settings/notifications/discord', regex: /\/settings\/notifications\/discord/, }, + { + text: 'Pushbullet', + content: ( + + + Pushbullet + + ), + route: '/settings/notifications/pushbullet', + regex: /\/settings\/notifications\/pushbullet/, + }, + { + text: 'Pushover', + content: ( + + + Pushover + + ), + route: '/settings/notifications/pushover', + regex: /\/settings\/notifications\/pushover/, + }, { text: 'Telegram', content: ( diff --git a/src/context/LanguageContext.tsx b/src/context/LanguageContext.tsx index 2114274c7..889cbea0f 100644 --- a/src/context/LanguageContext.tsx +++ b/src/context/LanguageContext.tsx @@ -14,6 +14,7 @@ export type AvailableLocale = | 'hu' | 'nb-NO' | 'nl' + | 'pl' | 'pt-BR' | 'pt-PT' | 'ru' @@ -72,6 +73,10 @@ export const availableLanguages: AvailableLanguageObject = { code: 'nb-NO', display: 'Norsk Bokmål', }, + pl: { + code: 'pl', + display: 'Polski', + }, 'pt-BR': { code: 'pt-BR', display: 'Português (Brasil)', @@ -94,7 +99,7 @@ export const availableLanguages: AvailableLanguageObject = { }, sr: { code: 'sr', - display: 'српски језик‬', + display: 'српски језик', }, ja: { code: 'ja', @@ -102,11 +107,11 @@ export const availableLanguages: AvailableLanguageObject = { }, 'zh-TW': { code: 'zh-TW', - display: '‪繁體中文‬', + display: '繁體中文', }, 'zh-CN': { code: 'zh-CN', - display: '‪简体中文‬', + display: '简体中文', }, }; diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts index 4c0ef7905..34a2b7101 100644 --- a/src/i18n/globalMessages.ts +++ b/src/i18n/globalMessages.ts @@ -49,6 +49,8 @@ const globalMessages = defineMessages({ 'Showing {from} to {to} of {total} results', resultsperpage: 'Display {pageSize} results per page', noresults: 'No results.', + open: 'Open', + resolved: 'Resolved', }); export default globalMessages; diff --git a/src/i18n/locale/ca.json b/src/i18n/locale/ca.json index 8f472a06c..303c680f7 100644 --- a/src/i18n/locale/ca.json +++ b/src/i18n/locale/ca.json @@ -6,7 +6,7 @@ "components.UserList.deleteconfirm": "Esteu segur que voleu suprimir aquest usuari? S'eliminaran totes les sol·licituds de forma permanent.", "components.UserList.creating": "S'està creant …", "components.UserList.createlocaluser": "Crea un usuari local", - "components.UserList.created": "Creat", + "components.UserList.created": "Registre", "components.UserList.create": "Crea", "components.UserList.bulkedit": "Edició massiva", "components.UserList.autogeneratepassword": "Genereu automàticament una contrasenya", @@ -44,7 +44,7 @@ "components.RequestModal.AdvancedRequester.animenote": "* Aquesta sèrie es un anime.", "components.RequestModal.AdvancedRequester.advancedoptions": "Avançat", "components.RequestList.sortModified": "Última modificació", - "components.RequestList.sortAdded": "Data de sol·licitud", + "components.RequestList.sortAdded": "Més recent", "components.RequestList.showallrequests": "Mostra totes les sol·licituds", "components.MovieDetails.studio": "{studioCount, plural, one {Estudis} other {Estudis}}", "components.RequestList.RequestItem.requested": "Sol·licitat", @@ -86,10 +86,10 @@ "components.PersonDetails.ascharacter": "com a {character}", "components.PersonDetails.appearsin": "Aspectes", "components.PersonDetails.alsoknownas": "També conegut com: {names}", - "components.PermissionEdit.viewrequestsDescription": "Concedeix permís per veure les sol·licituds d'altres usuaris.", + "components.PermissionEdit.viewrequestsDescription": "Concedeix permís per veure les sol·licituds de continguts d'altres usuaris.", "components.PermissionEdit.viewrequests": "Veure sol·licituds", "components.PermissionEdit.users": "Gestiona els usuaris", - "components.PermissionEdit.settingsDescription": "Concedeix permís per modificar els paràmetres Overseerr. Un usuari ha de tenir aquest permís per concedir-lo a altres persones.", + "components.PermissionEdit.settingsDescription": "Concedeix permís per modificar els paràmetres globals. Un usuari ha de tenir aquest permís per concedir-lo a altres persones.", "components.PermissionEdit.settings": "Gestiona la configuració", "components.PermissionEdit.requestDescription": "Concedeix permís per sol·licitar contingut no 4K.", "components.PermissionEdit.request4kTvDescription": "Concedeix permís per sol·licitar sèrie en 4K.", @@ -97,25 +97,24 @@ "components.PermissionEdit.request4kMoviesDescription": "Concedeix permís per sol·licitar pel·lícules en 4K.", "components.PermissionEdit.request4kMovies": "Sol·liciteu pel·lícules en 4K", "components.PermissionEdit.request4kDescription": "Concedeix permís per sol·licitar contingut en 4K.", - "components.PermissionEdit.managerequestsDescription": "Concedeix permís per gestionar les sol·licituds d'Overseerr. Totes les sol·licituds que faci un usuari amb aquest permís s’aprovaran automàticament.", + "components.PermissionEdit.managerequestsDescription": "Concedeix permís per gestionar les sol·licituds de contingut d'Overseerr. Totes les sol·licituds que faci un usuari amb aquest permís s’aprovaran automàticament.", "components.PermissionEdit.managerequests": "Gestiona les sol·licituds", "components.PermissionEdit.autoapproveSeriesDescription": "Concedeix l’aprovació automàtica de les sol·licituds de sèries que no siguin 4K.", "components.PermissionEdit.autoapproveSeries": "Aprovació automàtica de sèries", "components.PermissionEdit.autoapproveMoviesDescription": "Concedeix l’aprovació automàtica de les sol·licituds de pel·lícules que no siguin 4K.", "components.PermissionEdit.autoapproveMovies": "Aprovació automàtica de pel·lícules", - "components.PermissionEdit.autoapproveDescription": "Concedeix l’aprovació automàtica a totes les sol·licituds que no siguin 4K.", + "components.PermissionEdit.autoapproveDescription": "Concedeix l’aprovació automàtica a totes les sol·licituds de contingut que no siguin 4K.", "components.PermissionEdit.autoapprove4kSeriesDescription": "Concedeix l'aprovació automàtica de les sol·licituds de sèries 4K.", "components.PermissionEdit.autoapprove4kSeries": "Aprovació automàtica Sèries en 4K", "components.PermissionEdit.autoapprove4kMoviesDescription": "Concedeix l’aprovació automàtica per a les sol·licituds de pel·lícules 4K.", "components.PermissionEdit.autoapprove4kMovies": "Aprova automàticament pel·lícules 4K", - "components.PermissionEdit.autoapprove4kDescription": "Concedeix l’aprovació automàtica a totes les sol·licituds 4K.", + "components.PermissionEdit.autoapprove4kDescription": "Concedeix l’aprovació automàtica a totes les sol·licituds de contingut 4K.", "components.PermissionEdit.autoapprove4k": "Aprovació automàtica 4K", "components.PermissionEdit.autoapprove": "Aprovació automàtica", - "components.MovieDetails.openradarr": "Obre la pel·lícula a Radarr", "components.NotificationTypeSelector.mediadeclined": "Continguts rebutjats", "components.NotificationTypeSelector.mediarequested": "Contingut sol·licitat", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envieu notificacions quan els usuaris envien sol·licituds de suports nous que s’aprovin automàticament.", - "components.PermissionEdit.advancedrequestDescription": "Dona permís per utilitzar opcions avançades en les sol·licituds.", + "components.PermissionEdit.advancedrequestDescription": "Dona permís per modificar opcions avançades en les sol·licituds.", "components.PermissionEdit.advancedrequest": "Sol·licituds avançades", "components.PermissionEdit.adminDescription": "Accés complet d'administrador. Ignora totes les altres comprovacions de permisos.", "components.PermissionEdit.admin": "Administrador", @@ -132,7 +131,7 @@ "components.MovieDetails.viewfullcrew": "Veure equip complet", "components.MovieDetails.similar": "Títols similars", "components.MovieDetails.runtime": "Ingressos", - "components.MovieDetails.releasedate": "Data de publicació", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Data} other {Dates}} de llançament", "components.MovieDetails.recommendations": "Recomanacions", "components.MovieDetails.play4konplex": "Reprodueix en 4K a Plex", "components.MovieDetails.playonplex": "Reprodueix a Plex", @@ -140,15 +139,13 @@ "components.MovieDetails.overview": "Visió general", "components.MovieDetails.originaltitle": "Títol original", "components.MovieDetails.originallanguage": "Idioma original", - "components.MovieDetails.openradarr4k": "Obre la pel·lícula a Radarr", "components.MovieDetails.markavailable": "Marca com a disponible", "components.MovieDetails.mark4kavailable": "Marca com a disponible en 4K", - "components.MovieDetails.manageModalTitle": "Gestiona la pel·lícula", "components.Settings.RadarrModal.validationProfileRequired": "Heu de seleccionar un perfil de qualitat", "components.Settings.RadarrModal.validationPortRequired": "Heu de proporcionar un número de port vàlid", "components.Settings.RadarrModal.validationNameRequired": "Heu de proporcionar un nom de servidor", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Heu de seleccionar una disponibilitat mínima", - "components.Settings.RadarrModal.validationHostnameRequired": "Heu de proporcionar un nom d’amfitrió o una adreça IP", + "components.Settings.RadarrModal.validationHostnameRequired": "Heu de proporcionar un nom d’amfitrió o una adreça IP vàlides", "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "L'URL base no pot acabar amb una barra inclinada final", "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "L'URL base ha de tenir una barra inclinada", "components.Settings.RadarrModal.validationApplicationUrl": "Heu de proporcionar un URL vàlid", @@ -162,11 +159,6 @@ "components.Settings.RadarrModal.ssl": "Utilitza SSL", "components.Settings.RadarrModal.servername": "Nom del Servidor", "components.Settings.RadarrModal.server4k": "Servidor 4K", - "components.MovieDetails.manageModalRequests": "Sol·licituds", - "components.MovieDetails.manageModalNoRequests": "Sense sol·licituds.", - "components.MovieDetails.manageModalClearMediaWarning": "* Això eliminarà irreversiblement totes les dades d'aquesta pel·lícula, incloses les sol·licituds. Si aquest ítem existeix a la vostra biblioteca Plex, la informació de l'element es recrearà durant la pròxima exploració.", - "components.MovieDetails.manageModalClearMedia": "Esborra les dades de suports", - "components.MovieDetails.downloadstatus": "Estat de la baixada", "components.MovieDetails.cast": "Repartiment", "components.MovieDetails.budget": "Pressupost", "components.MovieDetails.MovieCrew.fullcrew": "Equip complet", @@ -213,11 +205,8 @@ "components.Discover.DiscoverNetwork.networkSeries": "Sèries de {network}", "components.Discover.DiscoverMovieLanguage.languageMovies": "Pel·lícules en {language}", "components.Discover.DiscoverMovieGenre.genreMovies": "Pel·lícules de {genre}", - "components.CollectionDetails.requestswillbecreated4k": "Els següents títols tenen sol·licituds creades en 4K:", - "components.CollectionDetails.requestswillbecreated": "Els següents títols tenen sol·licituds creades:", "components.CollectionDetails.requestcollection4k": "Sol·licita Col·lecció en 4K", "components.CollectionDetails.requestcollection": "Sol·licita Col·lecció", - "components.CollectionDetails.requestSuccess": " {title} s'ha sol·licitat correctament!", "components.CollectionDetails.overview": "Sinopsi", "components.CollectionDetails.numberofmovies": "{count} Pel·lícules", "components.AppDataWarning.dockerVolumeMissingDescription": "El muntatge de volum {appDataPath} no s'ha configurat correctament. Totes les dades s’esborraran quan el contenidor s’aturi o es reiniciï.", @@ -225,7 +214,6 @@ "components.RequestModal.requesterror": "S'ha produït un error en enviar la sol·licitud.", "components.RequestModal.requestedited": "Sol·licitud per a {title} editada correctament!", "components.RequestModal.requestcancelled": "S'ha cancel·lat la sol·licitud de {title}.", - "components.RequestModal.requestall": "Sol·licita totes les temporades", "components.RequestModal.requestadmin": "Aquesta sol·licitud s'aprovarà automàticament.", "components.RequestModal.requestCancel": "S'ha cancel·lat la sol·licitud de {title}.", "components.RequestModal.requestSuccess": "{title} s'ha sol·licitat correctament!", @@ -416,25 +404,14 @@ "components.TvDetails.overview": "Sinopsi", "components.TvDetails.originaltitle": "Títol original", "components.TvDetails.originallanguage": "Idioma original", - "components.TvDetails.opensonarr4k": "Obre la sèrie 4K a Sonarr", - "components.TvDetails.opensonarr": "Obre la sèrie a Sonarr", "components.TvDetails.nextAirDate": "Pròxima data d'emissió", "components.TvDetails.network": "{networkCount, plural, one {Plataforma} other {Plataformes}}", "components.StatusChacker.reloadOverseerr": "Torna a Carregar", - "components.TvDetails.markavailable": "Marca com a disponible", - "components.TvDetails.mark4kavailable": "Marca com a disponible en 4K", - "components.TvDetails.manageModalTitle": "Gestiona les sèries", - "components.TvDetails.manageModalRequests": "Sol·licituds", - "components.TvDetails.manageModalNoRequests": "Sense sol·licituds.", - "components.TvDetails.manageModalClearMediaWarning": "* Això eliminarà irreversiblement totes les dades d'aquesta sèrie, incloses les sol·licituds. Si aquest ítem existeix a la vostra biblioteca Plex, la informació multimèdia es recrearà durant la propera exploració.", - "components.TvDetails.manageModalClearMedia": "Esborra les dades de suports", "components.TvDetails.firstAirDate": "Primera data d'emissió", "components.TvDetails.episodeRuntimeMinutes": "{runtime} minuts", "components.TvDetails.episodeRuntime": "Duració de l'episodi", - "components.TvDetails.downloadstatus": "Estat de la baixada", "components.TvDetails.cast": "Repartiment", "components.TvDetails.anime": "Anime", - "components.TvDetails.allseasonsmarkedavailable": "* Totes les temporades estaran marcades com a disponibles.", "components.TvDetails.TvCrew.fullseriescrew": "Equip complet de la sèrie", "components.TvDetails.TvCast.fullseriescast": "Repartiment complet de la sèrie", "components.StatusChacker.newversionavailable": "Actualització de l'aplicació", @@ -540,7 +517,7 @@ "components.Settings.SonarrModal.validationPortRequired": "Heu de proporcionar un número de port vàlid", "components.Settings.SonarrModal.validationNameRequired": "Heu de proporcionar un nom de servidor", "components.Settings.SonarrModal.validationLanguageProfileRequired": "Heu de seleccionar un perfil d'idioma", - "components.Settings.SonarrModal.validationHostnameRequired": "Heu de proporcionar un nom d’amfitrió o una adreça IP", + "components.Settings.SonarrModal.validationHostnameRequired": "Heu de proporcionar un nom d’amfitrió o una adreça IP vàlides", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "L'URL base no pot acabar amb una barra inclinada final", "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "L'URL base ha de tenir una barra inclinada", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "L'URL no pot acabar amb una barra inclinada final", @@ -613,7 +590,7 @@ "components.Settings.SettingsAbout.githubdiscussions": "Debats de GitHub", "components.Settings.SettingsAbout.Releases.viewongithub": "Visualitza a GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Visualitza el registre de canvis", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Registre de canvis de versions", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Registre de canvis {version}", "components.Settings.SettingsUsers.movieRequestLimitLabel": "Límit global de sol·licituds de pel·lícules", "components.Settings.SettingsUsers.defaultPermissions": "Permisos per defecte", "components.Settings.SettingsLogs.showall": "Mostra tots els registres", @@ -644,11 +621,11 @@ "components.Settings.SettingsAbout.version": "Versió", "components.Settings.SettingsAbout.timezone": "Zona horària", "components.Settings.SettingsAbout.supportoverseerr": "Dóna suport a Overseerr", - "components.Settings.SettingsAbout.overseerrinformation": "Informació d'Overseerr", + "components.Settings.SettingsAbout.overseerrinformation": "Sobre Overseerr", "components.Settings.SettingsAbout.helppaycoffee": "Ajudeu-me convidant-me a un cafè", "components.Settings.SettingsAbout.documentation": "Documentació", "components.Settings.SettingsAbout.about": "Quant a", - "components.Settings.SettingsAbout.Releases.currentversion": "Versió actual", + "components.Settings.SettingsAbout.Releases.currentversion": "Actualitzat", "components.Settings.RadarrModal.selectQualityProfile": "Seleccioneu un perfil de qualitat", "components.Settings.RadarrModal.selectMinimumAvailability": "Seleccioneu la disponibilitat mínima", "components.Settings.RadarrModal.minimumAvailability": "Disponibilitat mínima", @@ -667,7 +644,7 @@ "components.Settings.Notifications.emailsettingsfailed": "No s'ha pogut desar la configuració de les notificacions per correu electrònic.", "components.Settings.RadarrModal.apiKey": "Clau API", "components.Settings.SettingsAbout.Releases.releases": "Versions", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "La informació de la versió no està disponible. GitHub està fora de línia?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "La informació de la versió no està disponible.", "components.Settings.SettingsAbout.Releases.latestversion": "Última versió", "components.Settings.RadarrModal.validationRootFolderRequired": "Heu de seleccionar una carpeta arrel", "components.Settings.RadarrModal.rootfolder": "Carpeta arrel", @@ -696,10 +673,9 @@ "components.UserList.usercreatedfailed": "S'ha produït un error en crear l'usuari.", "components.UserList.user": "Usuari", "components.UserList.totalrequests": "Sol·licituds", - "components.UserList.sortUpdated": "Última actualització", "components.UserList.sortRequests": "Recompte de sol·licituds", "components.UserList.sortDisplayName": "Nom de visualització", - "components.UserList.sortCreated": "Data de creació", + "components.UserList.sortCreated": "Data de registre", "components.UserList.role": "Rol", "components.UserList.plexuser": "Usuari de Plex", "components.UserList.passwordinfodescription": "Configureu l'URL d'una aplicació i activeu les notificacions per correu electrònic per permetre la generació automàtica de contrasenyes.", @@ -707,7 +683,6 @@ "components.UserList.owner": "Propietari", "components.UserList.nouserstoimport": "No hi ha usuaris nous a importar des de Plex.", "components.UserList.localuser": "Usuari local", - "components.UserList.lastupdated": "Actualitzat", "components.UserList.importfromplexerror": "S'ha produït un error en importar usuaris de Plex.", "components.UserList.importfromplex": "Importeu usuaris de Plex", "components.UserList.importedfromplex": "{userCount, plural, one {# nou usuari} other {# nous usuaris}} importat/s de Plex amb èxit!", @@ -827,7 +802,7 @@ "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "S'ha enviat la notificació de prova de LunaSea!", "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "S'està enviant la notificació de prova de LunaSea…", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "La configuració de les notificacions de LunaSea s'ha desat correctament!", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Només és necessari si no s'utilitza el perfil predeterminat", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Només és necessari si no s'utilitza el perfil default", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Nom de perfil", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Activa l'agent", "components.PermissionEdit.requestTvDescription": "Concedeix permís per sol·licitar sèries no 4K.", @@ -879,5 +854,143 @@ "components.MovieDetails.showmore": "Mostra més", "components.MovieDetails.showless": "Mostra menys", "components.Layout.LanguagePicker.displaylanguage": "Idioma de visualització", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.areyousuredelete": "Esteu segur que voleu suprimir aquest comentari?", + "components.IssueDetails.IssueComment.delete": "Suprimeix el comentari", + "components.IssueDetails.IssueComment.edit": "Edita el comentari", + "components.IssueDetails.IssueDescription.deleteissue": "Suprimeix la incidència", + "components.IssueDetails.IssueDescription.description": "Descripció", + "components.IssueDetails.IssueComment.postedby": "Publicat per {username} el {relativeTime}", + "components.IssueDetails.IssueComment.postedbyedited": "Publicat per {username} el {relativeTime} (Modificat)", + "components.IssueDetails.IssueComment.validationComment": "Heu d'introduir un missatge", + "components.IssueDetails.allepisodes": "Tots els episodis", + "components.IssueDetails.comments": "Comentaris", + "components.IssueDetails.issuepagetitle": "Incidència", + "components.IssueDetails.IssueDescription.edit": "Edita la descripció", + "components.IssueDetails.allseasons": "Totes les temporades", + "components.IssueDetails.closeissue": "Tanca la incidència", + "components.IssueDetails.closeissueandcomment": "Tanca amb comentaris", + "components.IssueDetails.deleteissue": "Suprimeix la incidència", + "components.IssueDetails.leavecomment": "Comentari", + "components.IssueDetails.deleteissueconfirm": "Esteu segur que voleu suprimir aquesta incidència?", + "components.IssueDetails.episode": "Episodi {episodeNumber}", + "components.IssueDetails.lastupdated": "Última actualització", + "components.IssueDetails.openinarr": "Oberta en {arr}", + "components.IssueDetails.toasteditdescriptionfailed": "S'ha produït un error en editar la descripció de la incidència.", + "components.IssueDetails.toastissuedeletefailed": "S'ha produït un error en suprimir la incidència.", + "components.IssueDetails.unknownissuetype": "Desconegut", + "components.IssueList.IssueItem.openeduserdate": "{date} per {user}", + "components.IssueDetails.nocomments": "Sense comentaris.", + "components.IssueDetails.issuetype": "Tipus", + "components.IssueDetails.openin4karr": "Obre en {arr} 4K", + "components.IssueDetails.problemseason": "Temporada afectada", + "components.IssueDetails.reopenissueandcomment": "Torna a obrir amb comentaris", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Temporada} other {Temporades}}", + "components.IssueDetails.toastissuedeleted": "La incidència s'ha suprimit correctament!", + "components.IssueDetails.toaststatusupdated": "L'estat de l'incidència s'ha actualitzat correctament!", + "components.IssueList.IssueItem.issuestatus": "Estat", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Heu de proporcionar una descripció", + "components.IssueModal.CreateIssueModal.whatswrong": "Què passa?", + "components.IssueModal.issueAudio": "Àudio", + "components.IssueModal.issueOther": "Altre", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Això eliminarà de manera irreversible totes les dades de {mediaType}, incloses les sol·licituds. Si aquest element existeix a la vostra biblioteca Plex, la informació dels continguts es recrearà durant la següent exploració.", + "components.ManageSlideOver.downloadstatus": "Estat de la descàrrega", + "components.IssueDetails.toasteditdescriptionsuccess": "La descripció de l'incidència s'ha editat correctament!", + "components.IssueList.IssueItem.issuetype": "Tipus", + "components.IssueList.IssueItem.problemepisode": "Episodi afectat", + "components.IssueDetails.problemepisode": "Episodi afectat", + "components.IssueDetails.openedby": "#{issueId} oberta {relativeTime} per {username}", + "components.IssueDetails.play4konplex": "Veure en 4K a Plex", + "components.IssueDetails.reopenissue": "Torna a obrir la incidència", + "components.IssueDetails.season": "Temporada {seasonNumber}", + "components.IssueList.IssueItem.unknownissuetype": "Desconegut", + "components.IssueList.IssueItem.viewissue": "Veure incidència", + "components.IssueModal.CreateIssueModal.allepisodes": "Tots els episodis", + "components.IssueModal.CreateIssueModal.allseasons": "Totes les temporades", + "components.IssueDetails.playonplex": "Veure a Plex", + "components.IssueList.IssueItem.opened": "Oberta", + "components.IssueList.issues": "Incidències", + "components.IssueModal.CreateIssueModal.reportissue": "Informar d'una incidència", + "components.IssueModal.CreateIssueModal.season": "Temporada {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Envia la incidència", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "S'ha produït un error en enviar la incidència.", + "components.IssueList.showallissues": "Mostra tots les incidències", + "components.IssueModal.CreateIssueModal.problemepisode": "Episodi afectat", + "components.IssueModal.CreateIssueModal.providedetail": "Si us plau, proporcioneu una explicació detallada del problema que heu trobat.", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "L'informe de la incidència per a {title} s'ha enviat correctament!", + "components.IssueModal.CreateIssueModal.problemseason": "Temporada afectada", + "components.IssueModal.CreateIssueModal.episode": "Episodi {episodeNumber}", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Hi ha algun problema amb {title}?", + "components.IssueList.sortModified": "Última modificació", + "components.IssueList.sortAdded": "Més recent", + "components.NotificationTypeSelector.issuecommentDescription": "Envia notificacions quan les incidències rebin comentaris nous.", + "components.NotificationTypeSelector.issuecreatedDescription": "Envieu notificacions quan s'informin d'incidències.", + "components.IssueModal.issueSubtitles": "Subtítol", + "components.NotificationTypeSelector.issueresolvedDescription": "Envieu notificacions quan es resolguin les incidències.", + "components.Layout.Sidebar.issues": "Incidències", + "components.NotificationTypeSelector.adminissuecommentDescription": "Notifica'm quan altres usuaris facin comentaris sobre incidències.", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Totes les temporades es marcaran com a disponibles.", + "components.ManageSlideOver.tvshow": "sèries", + "components.Settings.SettingsJobsCache.editJobSchedule": "Modifica la tasca programada", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Freqüència", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Cada {jobScheduleHours, plural, one {hora} other {{jobScheduleHours} hores}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Cada {jobScheduleMinutes, plural, one {minut} other {{jobScheduleMinutes} minuts}}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registreu una aplicació per utilitzar-la amb {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "El vostre identificador d'usuari o grup de 30 caràcters", + "components.MovieDetails.streamingproviders": "Actualment s'està retransmetent", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "S'ha produït un error en desar la tasca programada.", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Tasca programada editada correctament!", + "components.IssueModal.issueVideo": "Vídeo", + "components.NotificationTypeSelector.issuecomment": "Comentari de la incidència", + "components.NotificationTypeSelector.issuecreated": "Incidència informada", + "components.PermissionEdit.manageissuesDescription": "Doneu permís per gestionar incidències en continguts.", + "components.IssueDetails.toaststatusupdatefailed": "Alguna cosa ha fallat en actualitzar l'estat de la incidència.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Testimoni d'accés", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Creeu un testimoni des del vostra configuració del compte", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "La configuració de les notificacions Pushbullet no s'ha pogut desar.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "La configuració de notificació Pushbullet s'ha desat correctament!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Testimoni API de l'aplicació", + "components.IssueList.IssueItem.episodes": "{EpisodeCount, plural, one {Episodi} other {Episodis}}", + "components.IssueModal.CreateIssueModal.toastviewissue": "Veure incidència", + "components.ManageSlideOver.manageModalClearMedia": "Esborra les dades dels continguts", + "components.ManageSlideOver.manageModalNoRequests": "Sense sol·licituds.", + "components.ManageSlideOver.manageModalRequests": "Sol·licituds", + "components.ManageSlideOver.manageModalTitle": "Gestiona {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Marca com a disponible en 4K", + "components.ManageSlideOver.markavailable": "Marca com a disponible", + "components.ManageSlideOver.movie": "pel·lícula", + "components.ManageSlideOver.openarr": "Obre a {arr}", + "components.ManageSlideOver.openarr4k": "Obre en 4K {arr}", + "components.NotificationTypeSelector.issueresolved": "Incidència resolta", + "components.PermissionEdit.createissues": "Incidències informades", + "components.PermissionEdit.viewissues": "Veure incidències", + "components.PermissionEdit.viewissuesDescription": "Doneu permís per veure incidències de continguts informats per altres usuaris.", + "components.PermissionEdit.createissuesDescription": "Doneu permís per informar incidències en continguts.", + "components.PermissionEdit.manageissues": "Gestiona les incidències", + "components.Settings.SettingsAbout.runningDevelop": "Esteu executant la branca develop d'Overseerr, que només es recomana per a aquells que contribueixen al desenvolupament o ajuden amb proves de nous desenvolupaments.", + "components.TvDetails.streamingproviders": "Actualment s'està retransmetent", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Clau d'usuari o grup", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "La configuració de notificacions Pushover no s'ha pogut desar.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "La configuració de notificació Pushover s'ha desat correctament!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Heu de proporcionar un testimoni d'accés", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Heu de proporcionar un testimoni d'aplicació vàlid", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Heu de proporcionar una clau d'usuari o grup vàlida", + "i18n.open": "Oberta", + "i18n.resolved": "Resolta", + "components.NotificationTypeSelector.userissuecommentDescription": "Notifica'm quan incidències reportades per mi rebin comentaris nous.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Notifica'm quan altres usuaris informen incidències.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Notifica'm quan es resolguin incidències reportades per mi.", + "components.ManageSlideOver.manageModalIssues": "Incidències obertes", + "components.IssueModal.CreateIssueModal.extras": "Extres", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Notifica'm quan es tornin a obrir incidències per altres usuaris.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Notifica'm quan altres usuaris resolguin incidències.", + "components.NotificationTypeSelector.issuereopened": "Incidències reobertes", + "components.NotificationTypeSelector.issuereopenedDescription": "Notifica'm quan es tornin a obrir incidències.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Notifica'm quan es tornin a obrir incidències reportades per mi.", + "components.RequestModal.requestmovies4k": "{count} {count, plural, one {Pel·lícula} other {Pel·lícules}} en 4K solicitada/es", + "components.RequestModal.requestseasons4k": "{seasonCount} {seasonCount, plural, one {temporada} other {temporades}} solicitada/es", + "components.RequestModal.selectmovies": "Selecciona les pel·lícules", + "components.TvDetails.productioncountries": "{countryCount, plural, one {País} other {Països}} de producció", + "components.RequestModal.requestmovies": "{count} {count, plural, one {Pel·lícula} other {Pel·lícules}} solicitada/es", + "components.MovieDetails.productioncountries": "{countryCount, plural, one {País} other {Països}} de producció" } diff --git a/src/i18n/locale/cs.json b/src/i18n/locale/cs.json index 881bc5ab3..fd8ef3ba4 100644 --- a/src/i18n/locale/cs.json +++ b/src/i18n/locale/cs.json @@ -147,7 +147,6 @@ "components.UserList.role": "Role", "components.UserList.password": "Heslo", "components.UserList.owner": "Vlastník", - "components.UserList.lastupdated": "Aktualizováno", "components.UserList.creating": "Vytváření…", "components.UserList.created": "Vytvořeno", "components.UserList.create": "Vytvořit", @@ -155,7 +154,6 @@ "components.UserList.accounttype": "Typ", "components.TvDetails.recommendations": "Doporučení", "components.TvDetails.overview": "Přehled", - "components.TvDetails.manageModalRequests": "Žádosti", "components.TvDetails.cast": "Obsazení", "components.TvDetails.anime": "Anime", "components.StatusChacker.reloadOverseerr": "Znovu načíst", @@ -347,16 +345,8 @@ "components.MovieDetails.overview": "Přehled", "components.MovieDetails.originaltitle": "Původní název", "components.MovieDetails.originallanguage": "Původní jazyk", - "components.MovieDetails.openradarr4k": "Otevřít film ve 4K Radarru", - "components.MovieDetails.openradarr": "Otevřít film v Radarru", "components.MovieDetails.markavailable": "Označit jako dostupné", "components.MovieDetails.mark4kavailable": "Označit jako dostupné ve 4K", - "components.MovieDetails.manageModalTitle": "Spravovat film", - "components.MovieDetails.manageModalRequests": "Žádosti", - "components.MovieDetails.manageModalNoRequests": "Žádné žádosti.", - "components.MovieDetails.manageModalClearMediaWarning": "* Tímto nevratně odstraníte všechna data pro tento film, včetně všech požadavků. Pokud tato položka v knihovně Plex existuje, budou informace o médiu znovu vytvořeny při příštím skenování.", - "components.MovieDetails.manageModalClearMedia": "Vymazat data médií", - "components.MovieDetails.downloadstatus": "Stav stahování", "components.MovieDetails.cast": "Obsazení", "components.MovieDetails.budget": "Rozpočet", "components.MovieDetails.MovieCast.fullcast": "Kompletní obsazení", @@ -406,17 +396,14 @@ "components.Discover.TvGenreSlider.tvgenres": "Žánry seriálů", "components.Discover.TvGenreList.seriesgenres": "Žánry seriálů", "components.Discover.StudioSlider.studios": "Studia", - "components.Discover.NetworkSlider.networks": "TV sítě", + "components.Discover.NetworkSlider.networks": "Streamovací platformy", "components.Discover.MovieGenreSlider.moviegenres": "Filmové žánry", "components.Discover.MovieGenreList.moviegenres": "Filmové žánry", "components.CollectionDetails.numberofmovies": "{count} Filmů", "components.AppDataWarning.dockerVolumeMissingDescription": "Připojení svazku {appDataPath} nebylo správně nakonfigurováno. Všechna data budou vymazána při zastavení nebo opětovném spuštění kontejneru.", - "components.CollectionDetails.requestSuccess": " {title} úspěšně požádáno!", "components.Discover.DiscoverStudio.studioMovies": "{studio} Filmy", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Filmy", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Filmy", - "components.CollectionDetails.requestswillbecreated4k": "Pro následující tituly budou vytvořeny požadavky ve 4K:", - "components.CollectionDetails.requestswillbecreated": "Pro následující tituly budou vytvořeny požadavky:", "components.CollectionDetails.requestcollection4k": "Požádat o kolekci ve 4K", "components.CollectionDetails.requestcollection": "Požádat o kolekci", "components.CollectionDetails.overview": "Přehled", @@ -438,5 +425,119 @@ "components.Settings.startscan": "Spustit skenování", "components.Settings.serverpresetManualMessage": "Manuální konfigurace", "components.Settings.sonarrsettings": "Nastavení Sonarru", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueDescription.description": "Popis", + "components.IssueDetails.comments": "Komentáře", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frekvence", + "components.IssueModal.issueOther": "Jiný", + "components.RequestModal.requestadmin": "Tato žádost bude schválena automaticky.", + "components.IssueModal.issueAudio": "Zvuk", + "components.IssueModal.issueSubtitles": "Titulky", + "components.IssueModal.issueVideo": "Video", + "components.Layout.Sidebar.issues": "Problémy", + "components.ManageSlideOver.movie": "film", + "components.RequestModal.numberofepisodes": "Počet epizod", + "components.IssueDetails.issuepagetitle": "Problém", + "components.IssueDetails.leavecomment": "Komentář", + "components.ManageSlideOver.tvshow": "série", + "i18n.open": "Otevřené", + "components.IssueDetails.issuetype": "Typ", + "components.IssueList.IssueItem.opened": "Otevřené", + "components.IssueList.IssueItem.unknownissuetype": "Neznámý", + "components.IssueList.issues": "Problémy", + "components.ManageSlideOver.manageModalRequests": "Žádosti", + "i18n.resolved": "Vyřešeno", + "components.TvDetails.overviewunavailable": "Přehled není k dispozici.", + "components.TvDetails.watchtrailer": "Sledovat trailer", + "components.UserList.deleteuser": "Odstranit uživatele", + "components.UserList.email": "E-mailová adresa", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Výchozí ({language})", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Odeslat potichu", + "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", + "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "Nové heslo", + "components.UserProfile.movierequests": "Žádosti o filmy", + "components.UserList.localuser": "Místní uživatel", + "components.Settings.SettingsLogs.extraData": "Doplňující údaje", + "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Obecná nastavení", + "components.IssueModal.CreateIssueModal.toastviewissue": "Zobrazit problém", + "components.ManageSlideOver.manageModalNoRequests": "Žádné žádosti.", + "components.IssueList.IssueItem.issuetype": "Typ", + "components.IssueDetails.problemseason": "Ovlivněná série", + "components.IssueDetails.reopenissue": "Znovu otevřít problém", + "components.NotificationTypeSelector.issuecreated": "Problém nahlášen", + "components.PermissionEdit.viewissues": "Zobrazit problémy", + "components.ManageSlideOver.manageModalIssues": "Otevřené problémy", + "components.IssueModal.CreateIssueModal.problemepisode": "Ovlivněná epizoda", + "components.IssueDetails.IssueComment.delete": "Odstranit komentář", + "components.IssueDetails.IssueComment.edit": "Upravit komentář", + "components.IssueDetails.allseasons": "Všechny série", + "components.IssueDetails.closeissue": "Uzavřít problém", + "components.ManageSlideOver.downloadstatus": "Stav stahování", + "components.NotificationTypeSelector.issueresolved": "Problém vyřešen", + "components.RequestList.RequestItem.modifieduserdate": "{date} od {user}", + "components.RequestList.showallrequests": "Zobrazit všechny žádosti", + "components.IssueDetails.IssueDescription.deleteissue": "Odstranit problém", + "components.IssueDetails.IssueDescription.edit": "Upravit popis", + "components.IssueDetails.allepisodes": "Všechny epizody", + "components.IssueDetails.deleteissue": "Odstranit problém", + "components.IssueDetails.episode": "Epizoda {episodeNumber}", + "components.IssueDetails.lastupdated": "Poslední aktualizace", + "components.IssueDetails.unknownissuetype": "Neznámý", + "components.PersonDetails.lifespan": "{birthdate} – {deathdate}", + "components.RequestButton.declinerequest4k": "Odmítnout 4K žádost", + "components.ResetPassword.resetpasswordsuccessmessage": "Heslo bylo úspěšně resetováno!", + "components.IssueModal.CreateIssueModal.whatswrong": "Co je špatně?", + "components.RequestButton.approverequest4k": "Schválit 4K žádost", + "components.RequestButton.viewrequest4k": "Zobrazit 4K požadavek", + "components.RequestModal.requestSuccess": "{title} bylo úspěšně zažádáno!", + "components.Settings.SettingsLogs.logDetails": "Podrobnosti o záznamu", + "components.StatusBadge.status4k": "4K {status}", + "components.StatusChacker.newversionavailable": "Aktualizace aplikace", + "components.TvDetails.episodeRuntime": "Délka epizody", + "components.TvDetails.episodeRuntimeMinutes": "{runtime} minut", + "components.TvDetails.originallanguage": "Původní jazyk", + "components.TvDetails.originaltitle": "Původní název", + "components.UserList.bulkedit": "Hromadné úpravy", + "components.IssueDetails.nocomments": "Žádné komentáře.", + "components.IssueList.IssueItem.issuestatus": "Stav", + "components.IssueDetails.problemepisode": "Ovlivněná epizoda", + "components.IssueDetails.season": "Série {seasonNumber}", + "components.IssueList.IssueItem.problemepisode": "Ovlivněná epizoda", + "components.IssueList.IssueItem.viewissue": "Zobrazit problém", + "components.IssueList.sortAdded": "Nejnovější", + "components.IssueList.sortModified": "Naposledy změněno", + "components.IssueModal.CreateIssueModal.allepisodes": "Všechny epizody", + "components.IssueModal.CreateIssueModal.allseasons": "Všechny série", + "components.IssueModal.CreateIssueModal.episode": "Epizoda {episodeNumber}", + "components.IssueModal.CreateIssueModal.problemseason": "Ovlivněná série", + "components.IssueModal.CreateIssueModal.season": "Série {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Odeslat problém", + "components.ManageSlideOver.manageModalTitle": "Spravovat {mediaType}", + "components.PermissionEdit.createissues": "Nahlásit problémy", + "components.PermissionEdit.manageissues": "Správa problémů", + "components.ResetPassword.emailresetlink": "Odkaz na obnovení e-mailu", + "components.ResetPassword.validationpasswordmatch": "Hesla se musí shodovat", + "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "Musíte zadat platnou adresu URL", + "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Obnovit do výchozího nastavení", + "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "Musíte zadat platnou adresu URL", + "components.Settings.SettingsJobsCache.editJobSchedule": "Upravit úlohu", + "components.Setup.configureservices": "Konfigurovat služby", + "components.Setup.finish": "Dokončit nastavení", + "components.UserList.plexuser": "Plex uživatel", + "components.UserList.sortRequests": "Počet žádostí", + "components.UserList.userlist": "Seznam uživatelů", + "components.UserProfile.ProfileHeader.joindate": "Připojil se {joindate}", + "components.UserProfile.ProfileHeader.profile": "Zobrazit profil", + "components.UserProfile.ProfileHeader.settings": "Upravit nastavení", + "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Typ účtu", + "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Jazyk zobrazení", + "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Místní uživatel", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Jazyk pro objevování", + "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex uživatel", + "components.UserProfile.UserSettings.UserGeneralSettings.region": "Region pro objevování", + "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "ID uživatele", + "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Nastavení oznámení", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "ID chatu", + "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Potvrďte heslo", + "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Aktuální heslo" } diff --git a/src/i18n/locale/da.json b/src/i18n/locale/da.json index 7f2c663ba..c0ac82906 100644 --- a/src/i18n/locale/da.json +++ b/src/i18n/locale/da.json @@ -1,52 +1,990 @@ { - "components.CollectionDetails.requestSuccess": "Dit ønske er accepteret!", "components.Discover.discovermovies": "Populære Film", "components.MediaSlider.ShowMoreCard.seemore": "Se Mere", "components.Login.validationpasswordrequired": "Angiv et kodeord", "components.Login.validationemailrequired": "Angiv en gyldig email adresse", - "components.Login.signinwithplex": "Brug din Plex Konto", + "components.Login.signinwithplex": "Brug din Plex konto", "components.Login.signinheader": "Log ind for at forsætte", - "components.Login.signingin": "Logger ind…", - "components.Login.signin": "Log ind", + "components.Login.signingin": "Logger Ind…", + "components.Login.signin": "Log Ind", "components.Login.password": "Kodeord", - "components.Login.loginerror": "Noget gik galt, i dit forsøg på at logge ind.", - "components.Login.forgotpassword": "Glemt kodeord?", + "components.Login.loginerror": "Noget gik galt under log ind.", + "components.Login.forgotpassword": "Glemt Kodeord?", "components.Login.email": "Email Adresse", - "components.Layout.VersionStatus.streamdevelop": "Overseerr Udvikler", + "components.Layout.VersionStatus.streamdevelop": "Overseerr Develop", "components.Layout.VersionStatus.outofdate": "Forældet", "components.Layout.UserDropdown.signout": "Log ud", "components.Layout.UserDropdown.settings": "Indstillinger", "components.Layout.UserDropdown.myprofile": "Profil", "components.Layout.Sidebar.users": "Brugere", "components.Layout.Sidebar.settings": "Indstillinger", - "components.Layout.Sidebar.requests": "Ønsker", + "components.Layout.Sidebar.requests": "Forespørgsler", "components.Layout.Sidebar.dashboard": "Udforsk", "components.Layout.SearchInput.searchPlaceholder": "Søg Film & Serier", - "components.Layout.LanguagePicker.displaylanguage": "Vis Sprog", + "components.Layout.LanguagePicker.displaylanguage": "Grænsefladesprog", "components.LanguageSelector.originalLanguageDefault": "Alle Sprog", - "components.LanguageSelector.languageServerDefault": "Standard ({sprog})", + "components.LanguageSelector.languageServerDefault": "Standard ({language})", "components.Discover.upcomingtv": "Kommende Serier", "components.Discover.upcomingmovies": "Kommende Film", "components.Discover.upcoming": "Kommende Film", "components.Discover.trending": "Aktuelle", - "components.Discover.recentrequests": "Seneste Ønsker", + "components.Discover.recentrequests": "Seneste Forespørgsler", "components.Discover.recentlyAdded": "Nyligt tilføjet", "components.Discover.populartv": "Populære Serier", "components.Discover.popularmovies": "Populære Film", - "components.Discover.noRequests": "Ingen ønsker.", + "components.Discover.noRequests": "Ingen forespørgsler.", "components.Discover.discovertv": "Populære Serier", "components.Discover.discover": "Udforsk", - "components.Discover.TvGenreSlider.tvgenres": "Serie Genre", - "components.Discover.TvGenreList.seriesgenres": "Serie Genre", + "components.Discover.TvGenreSlider.tvgenres": "Seriegenrer", + "components.Discover.TvGenreList.seriesgenres": "Seriegenrer", "components.Discover.NetworkSlider.networks": "Netværk", - "components.Discover.MovieGenreSlider.moviegenres": "Film Genre", - "components.Discover.MovieGenreList.moviegenres": "Film Genre", + "components.Discover.MovieGenreSlider.moviegenres": "Filmgenrer", + "components.Discover.MovieGenreList.moviegenres": "Filmgenrer", "components.Discover.DiscoverStudio.studioMovies": "{studio} Film", - "components.Discover.DiscoverNetwork.networkSeries": "{netværk} Serier", - "components.Discover.DiscoverMovieLanguage.languageMovies": "{sprog} Film", + "components.Discover.DiscoverNetwork.networkSeries": "{network} Serier", + "components.Discover.DiscoverMovieLanguage.languageMovies": "Film på {language}", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Film", - "components.CollectionDetails.requestcollection4k": "Ønskesamling i 4k", - "components.CollectionDetails.requestcollection": "Ønskesamling", + "components.CollectionDetails.requestcollection4k": "Forespørgselssamling i 4K", + "components.CollectionDetails.requestcollection": "Forespørgselssamling", "components.CollectionDetails.overview": "Overblik", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.MovieDetails.originaltitle": "Originaltitel", + "components.AppDataWarning.dockerVolumeMissingDescription": "Volume mount'et, {appDataPath}, var ikke konfigureret korrekt. Al data vil blive slettet når containeren stoppes eller genstartes.", + "components.CollectionDetails.numberofmovies": "{count} Film", + "components.Discover.DiscoverTvGenre.genreSeries": "{genre} Serier", + "components.Discover.DiscoverTvLanguage.languageSeries": "Serier på {language}", + "components.Discover.StudioSlider.studios": "Studier", + "components.DownloadBlock.estimatedtime": "Estimeret {time}", + "components.MovieDetails.mark4kavailable": "Markér som Tilgængelig i 4K", + "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {ændring} other {ændringer}} bagud", + "components.Layout.VersionStatus.streamstable": "Overseerr Stable", + "components.MovieDetails.markavailable": "Markér som Tilgængelig", + "components.MovieDetails.originallanguage": "Originalsprog", + "components.Login.signinwithoverseerr": "Brug din {applicationTitle} konto", + "components.MovieDetails.MovieCast.fullcast": "Medvirkende", + "components.MovieDetails.MovieCrew.fullcrew": "Filmstab", + "components.MovieDetails.budget": "Budget", + "components.MovieDetails.overview": "Overblik", + "components.MovieDetails.recommendations": "Anbefalinger", + "components.MovieDetails.overviewunavailable": "Overblik ikke tilgængelig.", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Udgivelsesdato} other {Udgivelsesdatoer}}", + "components.MovieDetails.revenue": "Omsætning", + "components.MovieDetails.runtime": "{minutes} minutter", + "components.MovieDetails.play4konplex": "Afspil i 4K i Plex", + "components.MovieDetails.similar": "Lignende Titler", + "components.MovieDetails.streamingproviders": "Kan I Øjeblikket Streames På", + "components.MovieDetails.playonplex": "Afspil i Plex", + "components.MovieDetails.viewfullcrew": "Vis Filmstab", + "components.MovieDetails.watchtrailer": "Se Trailer", + "components.PermissionEdit.advancedrequestDescription": "Giv tilladelse til at modificere avancerede medieforespørgselsindstillinger.", + "components.PermissionEdit.autoapprove4kSeries": "Auto-Godkend 4K Serier", + "components.PermissionEdit.autoapprove4kMoviesDescription": "Giv automatisk godkendelse for 4K filmforespørgsler.", + "components.PermissionEdit.managerequests": "Administrér Forespørgsler", + "components.PermissionEdit.request4k": "Forespørg 4K", + "components.PermissionEdit.request4kMovies": "Forespørg 4K Film", + "components.PermissionEdit.request4kTv": "Forespørg 4K Serier", + "components.PermissionEdit.requestMovies": "Forespørg Film", + "components.PermissionEdit.request": "Forespørg", + "components.PersonDetails.ascharacter": "som {character}", + "components.PersonDetails.birthdate": "Født {birthdate}", + "components.RequestButton.approverequest4k": "Godkend 4K Forespørgsel", + "components.RequestButton.approverequests": "Godkend {requestCount, plural, one {Forespørgsel} other {{requestCount} Forespørgsler}}", + "components.RequestList.RequestItem.editrequest": "Redigér Forespørgsel", + "components.RequestList.RequestItem.mediaerror": "Den forbundne titel for denne forespørgsel er ikke længere tilgængelig.", + "components.RequestList.RequestItem.modified": "Ændret", + "components.RequestList.RequestItem.modifieduserdate": "{date} af {user}", + "components.RequestList.RequestItem.requesteddate": "Forespurgt", + "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Sæson} other {Sæsoner}}", + "components.RequestList.requests": "Forespørgsler", + "components.ResetPassword.resetpassword": "Nulstil dit kodeord", + "components.PermissionEdit.users": "Administrér Brugere", + "components.PermissionEdit.usersDescription": "Giv tilladelse til at adminstrere brugere. Brugere med denne rettighed kan ikke modificere brugere med eller give Admin rettigheder.", + "components.PermissionEdit.viewissuesDescription": "Giv tilladelse til at se medieproblemer rapporteret af andre brugere.", + "components.PermissionEdit.viewrequests": "Vis Forespørgsler", + "components.PersonDetails.alsoknownas": "Også Kendt Som: {names}", + "components.PersonDetails.appearsin": "Medvirket i", + "components.PersonDetails.crewmember": "Besætningsmedlem", + "components.PlexLoginButton.signingin": "Logger Ind…", + "components.PlexLoginButton.signinwithplex": "Log Ind", + "components.QuotaSelector.movieRequests": "{quotaLimit} {movies} per {quotaDays} {days}", + "components.QuotaSelector.seasons": "{count, plural, one {sæson} other {sæsoner}}", + "components.QuotaSelector.unlimited": "Ubegrænset", + "components.RequestBlock.profilechanged": "Kvalitetsprofil", + "components.RequestBlock.seasons": "{seasonCount, plural, one {Sæson} other {Sæsoner}}", + "components.RequestButton.approve4krequests": "Godkend {requestCount, plural, one {4K Forespørgsel} other {{requestCount} 4K Forespørgsler}}", + "components.RequestButton.approverequest": "Godkend Forespørgsel", + "components.RequestButton.decline4krequests": "Afvis {requestCount, plural, one {4K Forespørgsel} other {{requestCount} 4K Forespørgsler}}", + "components.RequestButton.declinerequest4k": "Afvis 4K Forespørgsel", + "components.RequestButton.requestmore4k": "Forespørg om Mere i 4K", + "components.RequestButton.viewrequest": "Vis Forespørgsel", + "components.RequestModal.QuotaDisplay.movie": "film", + "components.ResetPassword.password": "Kodeord", + "components.Search.searchresults": "Søgeresultater", + "components.IssueDetails.IssueComment.areyousuredelete": "Er du sikker på du vil slette denne kommentar?", + "components.IssueDetails.IssueComment.delete": "Slet Kommentar", + "components.IssueDetails.IssueComment.edit": "Redigér kommentar", + "components.IssueDetails.IssueComment.postedby": "Sendt {relativeTime} af {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Sendt {relativeTime} af {username} (Redigeret)", + "components.IssueDetails.problemepisode": "Påvirket Episode", + "components.ManageSlideOver.downloadstatus": "Download Status", + "components.ManageSlideOver.manageModalNoRequests": "Ingen forespørgsler.", + "components.ManageSlideOver.movie": "film", + "components.PersonDetails.lifespan": "{birthdate} – {deathdate}", + "components.QuotaSelector.movies": "{count, plural, one {film} other {film}}", + "components.RequestButton.requestmore": "Forespørg Flere", + "components.RequestModal.QuotaDisplay.quotaLinkUser": "Du kan se et overblik over denne brugers forespørgselsgrænser på deres profilside.", + "components.RequestModal.selectseason": "Vælg Sæson(er)", + "components.ResetPassword.confirmpassword": "Bekræft Kodeord", + "components.ResetPassword.email": "Email Adresse", + "components.ResetPassword.gobacklogin": "Vend Tilbage til Login-siden", + "components.IssueDetails.IssueDescription.deleteissue": "Slet Problem", + "components.IssueDetails.IssueDescription.description": "Beskrivelse", + "components.IssueDetails.IssueDescription.edit": "Redigér beskrivelse", + "components.IssueDetails.allepisodes": "Alle Episoder", + "components.IssueDetails.allseasons": "Alle Sæsoner", + "components.IssueDetails.closeissue": "Luk Problem", + "components.IssueDetails.closeissueandcomment": "Luk med Kommentar", + "components.IssueDetails.comments": "Kommentarer", + "components.IssueDetails.deleteissue": "Slet Problem", + "components.IssueDetails.deleteissueconfirm": "Er du sikker på du vil slette dette problem?", + "components.IssueDetails.episode": "Episode {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Problem", + "components.IssueDetails.lastupdated": "Sidst Opdateret", + "components.IssueDetails.leavecomment": "Kommentar", + "components.IssueDetails.openinarr": "Åbn i {arr}", + "components.IssueDetails.season": "Sæson {seasonNumber}", + "components.IssueDetails.toasteditdescriptionfailed": "Noget gik galt under redigeringen af problembeskrivelsen.", + "components.IssueDetails.toastissuedeletefailed": "Noget gik galt under sletningen af problemet.", + "components.IssueDetails.toaststatusupdatefailed": "Noget gik galt under opdateringen af problemstatus.", + "components.IssueDetails.unknownissuetype": "Ukendt", + "components.IssueModal.CreateIssueModal.problemepisode": "Påvirket Episode", + "components.PermissionEdit.viewissues": "Vis Problemer", + "components.RequestBlock.server": "Destinationsserver", + "components.IssueModal.CreateIssueModal.extras": "Ekstra", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Er der et problem med {title}?", + "components.IssueModal.CreateIssueModal.problemseason": "Påvirket Sæson", + "components.IssueModal.CreateIssueModal.providedetail": "Giv venligst en detaljeret beskrivelse af problemet du stødte på.", + "components.IssueModal.CreateIssueModal.reportissue": "Rapportér et Problem", + "components.IssueModal.CreateIssueModal.season": "Sæson {seasonNumber}", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Noget gik galt under indsendelsen af problemet.", + "components.IssueModal.CreateIssueModal.toastviewissue": "Vis Problem", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Du skal give en beskrivelse", + "components.PermissionEdit.viewrequestsDescription": "Giv tilladelse til at se medieforespørgsler indsendt af andre brugere.", + "components.RequestButton.declinerequest": "Afvis Forespørgsel", + "components.IssueDetails.playonplex": "Afspil i Plex", + "components.IssueDetails.problemseason": "Påvirket Sæson", + "components.IssueDetails.issuetype": "Type", + "components.IssueDetails.nocomments": "Ingen kommentarer.", + "components.IssueDetails.openedby": "#{issueId} åbnet {relativeTime} af {username}", + "components.IssueDetails.openin4karr": "Åbn i 4K {arr}", + "components.IssueDetails.play4konplex": "Afspil i 4K i Plex", + "components.IssueDetails.reopenissue": "Genåbn Problem", + "components.IssueDetails.reopenissueandcomment": "Genåbn med Kommentar", + "components.IssueDetails.toasteditdescriptionsuccess": "Problembeskrivelse er blevet redigeret!", + "components.IssueDetails.toastissuedeleted": "Problem er blevet slettet!", + "components.IssueDetails.toaststatusupdated": "Problemstatus er blevet opdateret!", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episode} other {Episoder}}", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.issuetype": "Type", + "components.IssueList.IssueItem.opened": "Åbnet", + "components.IssueList.IssueItem.openeduserdate": "{date} af {user}", + "components.IssueList.IssueItem.problemepisode": "Påvirket Episode", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Sæson} other {Sæsoner}}", + "components.IssueList.IssueItem.unknownissuetype": "Ukendt", + "components.IssueList.IssueItem.viewissue": "Vis Problem", + "components.IssueList.issues": "Problemer", + "components.IssueList.showallissues": "Vis Alle Problemer", + "components.IssueList.sortAdded": "Seneste", + "components.IssueList.sortModified": "Sidst Ændret", + "components.IssueModal.CreateIssueModal.allepisodes": "Alle Episoder", + "components.IssueModal.CreateIssueModal.allseasons": "Alle Sæsoner", + "components.IssueModal.CreateIssueModal.episode": "Episode {episodeNumber}", + "components.IssueModal.issueSubtitles": "Undertekst", + "components.IssueModal.issueVideo": "Video", + "components.Layout.Sidebar.issues": "Problemer", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Alle sæsoner vil blive markeret som tilgængelig.", + "components.ManageSlideOver.manageModalClearMedia": "Ryd Mediedata", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Dette vil slette alle data for denne {mediaType} uden mulighed for gendannelse, inklusiv alle forespørgsler. Hvis dette objekt findes i dit Plex bibliotek vil medieinformationen blive genskabt under næste skanning.", + "components.IssueModal.CreateIssueModal.whatswrong": "Hvad er galt?", + "components.IssueModal.issueAudio": "Lyd", + "components.IssueModal.issueOther": "Andet", + "components.ManageSlideOver.tvshow": "serier", + "components.MovieDetails.studio": "{studioCount, plural, one {Studie} other {Studier}}", + "components.NotificationTypeSelector.adminissuecommentDescription": "Bliv notificeret når andre brugere kommenterer på problemer.", + "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Send notifikationer når brugere indsender nye medieforespørgsler som automatisk godkendes.", + "components.ManageSlideOver.manageModalRequests": "Forespørgsler", + "components.ManageSlideOver.manageModalTitle": "Administrér {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Markér som Tilgængelig i 4K", + "components.ManageSlideOver.markavailable": "Markér som Tilgængelig", + "components.ManageSlideOver.openarr": "Åbn i {arr}", + "components.ManageSlideOver.openarr4k": "Åbn i 4K {arr}", + "components.PermissionEdit.autoapproveMovies": "Auto-Godkend Film", + "components.PermissionEdit.autoapproveSeries": "Auto-Godkend Serier", + "components.PermissionEdit.autoapprove4k": "Auto-Godkend 4K", + "components.PermissionEdit.autoapprove4kMovies": "Auto-Godkend 4K Film", + "components.PermissionEdit.requestTv": "Forespørg Serier", + "components.PermissionEdit.autoapproveMoviesDescription": "Giv automatisk godkendelse for alle ikke-4K filmforespørgsler.", + "components.PermissionEdit.requestTvDescription": "Bliv notificeret når problemer er genåbnet af andre brugere.", + "components.PermissionEdit.settings": "Administrér Indstillinger", + "components.RegionSelector.regionServerDefault": "Standard ({region})", + "components.RequestCard.deleterequest": "Slet Forespørgsel", + "components.RequestCard.mediaerror": "Den forbundne titel for denne forespørgsel er ikke længere tilgængelig.", + "components.RequestCard.seasons": "{seasonCount, plural, one {Sæson} other {Sæsoner}}", + "components.RequestList.RequestItem.cancelRequest": "Annullér Forespørgsel", + "components.RequestList.RequestItem.deleterequest": "Slet Forespørgsel", + "components.RequestModal.AdvancedRequester.default": "{name} (Standard)", + "components.RequestModal.AdvancedRequester.destinationserver": "Destinationsserver", + "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", + "components.RequestList.RequestItem.requested": "Forespurgt", + "components.RequestList.showallrequests": "Vis Alle Forespørgsler", + "components.RequestList.sortAdded": "Seneste", + "components.RequestList.sortModified": "Sidst Ændret", + "components.RequestModal.AdvancedRequester.advancedoptions": "Avanceret", + "components.RequestModal.AdvancedRequester.animenote": "* Denne serie er en anime.", + "components.RequestModal.AdvancedRequester.selecttags": "Vælg tags", + "components.RequestModal.AdvancedRequester.languageprofile": "Sprogprofil", + "components.RequestModal.AdvancedRequester.notagoptions": "Ingen tags.", + "components.RequestModal.AdvancedRequester.qualityprofile": "Kvalitetsprofil", + "components.RequestModal.AdvancedRequester.requestas": "Forespørg Som", + "components.RequestModal.AdvancedRequester.rootfolder": "Rodmappe", + "components.RequestModal.AdvancedRequester.tags": "Tags", + "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {film} other {film}}", + "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Ikke tilstrækkelig med sæsonforespørgsler tilbage", + "components.RequestModal.QuotaDisplay.quotaLink": "Du kan se et overblik af dine forespørgselsgrænser på din profilside.", + "components.RequestModal.QuotaDisplay.allowedRequests": "Du kan forespørge om {limit} {type} hver {days} dag.", + "components.RequestModal.QuotaDisplay.allowedRequestsUser": "Denne bruger kan forespørge om {limit} {type} hver {days} dag.", + "components.IssueDetails.IssueComment.validationComment": "Du skal skrive en besked", + "components.PermissionEdit.settingsDescription": "Giv tilladelse til at modificere globale indstillinger. En bruger skal have denne rettighed for at kunne give den til andre.", + "components.ManageSlideOver.manageModalIssues": "Åbne Problemer", + "components.MovieDetails.showless": "Vis Mindre", + "components.MovieDetails.cast": "Medvirkende", + "components.IssueModal.CreateIssueModal.submitissue": "Indsend Problem", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Problemrapport for {title} er blevet indsendt!", + "components.MovieDetails.showmore": "Vis Mere", + "components.PermissionEdit.autoapprove": "Auto-Godkend", + "components.QuotaSelector.days": "{count, plural, one {dag} other {dage}}", + "components.RequestBlock.requestoverrides": "Forespørgselstilsidesættelser", + "components.QuotaSelector.tvRequests": "{quotaLimit} {seasons} per {quotaDays} {days}", + "components.RequestButton.declinerequests": "Afvis {requestCount, plural, one {Forespørgsel} other {{requestCount} Forespørgsler}}", + "components.RegionSelector.regionDefault": "Alle Regioner", + "components.RequestBlock.rootfolder": "Rodmappe", + "components.RequestButton.viewrequest4k": "Vis 4K Forespørgsel", + "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Aktiver Agent", + "components.RequestModal.seasonnumber": "Vis Alle Forespørgsler", + "components.NotificationTypeSelector.mediadeclinedDescription": "Send notifikationer når medieforespørgsler afvises.", + "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "For at kunne modtage web push-notifikationer skal Overseerr benytte HTTPS.", + "components.NotificationTypeSelector.mediaavailable": "Medie Tilgængelig", + "components.NotificationTypeSelector.mediafailed": "Medie Fejlede", + "components.PermissionEdit.admin": "Administrator", + "components.PermissionEdit.adminDescription": "Fuld administratoradgang. Omgår alle andre tilladelseskontroller.", + "components.PermissionEdit.advancedrequest": "Avancerede Forespørgsler", + "components.PermissionEdit.autoapprove4kSeriesDescription": "Giv automatisk godkendelse for 4K serieforespørgsler.", + "components.PermissionEdit.managerequestsDescription": "Giv tilladelse til at administrere medieforespørgsler. Alle forespørgsler lavet af en bruger med denne tilladelse vil automatisk blive godkendt.", + "components.PermissionEdit.request4kDescription": "Giv tilladelse til at indsende forespørgsler for 4K medier.", + "components.PermissionEdit.request4kMoviesDescription": "Giv tilladelse til at indsende forespørgsler for 4K film.", + "components.PermissionEdit.request4kTvDescription": "Giv tilladelse til at indsende forespørgsler for 4K serier.", + "components.PermissionEdit.requestDescription": "Giv tilladelse til at indsende forespørgsler for ikke-4K medier.", + "components.RequestList.RequestItem.failedretry": "Noget gik galt ved nyt forsøg på forespørgslen.", + "components.RequestModal.QuotaDisplay.requiredquota": "Du skal mindst have {seasons} {seasons, plural, one {sæsonforespørgsel} other {sæsonforespørgsler}} tilbage for at indsende en forespørgsel på denne serie.", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "Denne bruger skal mindst have {seasons} {seasons, plural, one {sæsonforespørgsel} other {sæsonforespørgsler}} tilbage for at indsende en forespørgsel fore denne serie.", + "components.RequestModal.QuotaDisplay.season": "sæson", + "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {sæson} other {sæsoner}}", + "components.RequestModal.SearchByNameModal.nosummary": "Der kunne ikke findes en beskrivelse for denne titel.", + "components.RequestModal.alreadyrequested": "Allerede Forespurgt", + "components.RequestModal.cancel": "Annullér Forespørgsel", + "components.RequestModal.edit": "Redigér Forespørgsel", + "components.RequestModal.errorediting": "Noget gik galt under redigeringen af forespørgslen.", + "components.RequestModal.extras": "Ekstra", + "components.RequestModal.numberofepisodes": "Antal Episoder", + "components.RequestModal.pending4krequest": "Afventende 4K Forespørgsler for {title}", + "components.RequestModal.pendingapproval": "Din forespørgsel afventer godkendelse.", + "components.ResetPassword.resetpasswordsuccessmessage": "Kodeord er nulstillet!", + "components.Settings.Notifications.NotificationsLunaSea.profileName": "Profilnavn", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea testnotifikation er afsendt!", + "components.Settings.Notifications.NotificationsLunaSea.validationTypes": "Du skal vælge mindst én notifikationstype", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet testnotifikation kunne ikke sendes.", + "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Registrér en applikation til brug med Overseerr", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Pushover testnotifikation kunne ikke afsendes.", + "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Du skal angive en gyldig applikationstoken", + "components.Settings.Notifications.NotificationsPushover.validationTypes": "Du skal angive mindst én notifikationstype", + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Opret en Indkommende Webhook integration", + "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Aktivér Agent", + "components.Settings.Notifications.NotificationsWebhook.customJson": "JSON indhold", + "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Nulstil", + "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "Du skal angive et gyldigt URL", + "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.agentenabled": "Aktivér Agent", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Bliv notificeret når problemer er oprettet af andre brugere.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Bliv notificeret når problemer er løst af andre brugere.", + "components.NotificationTypeSelector.issuereopened": "Problem Genåbnet", + "components.NotificationTypeSelector.issuereopenedDescription": "Send notifikationer når problemer genåbnes.", + "components.NotificationTypeSelector.mediafailedDescription": "Send notifikationer når medieforespørgsler ikke kunne tilføjes til Radarr eller Sonarr.", + "components.NotificationTypeSelector.mediarequested": "Medie Forespurgt", + "components.NotificationTypeSelector.userissuecommentDescription": "Bliv notificeret når problemer du har rapporteret får nye kommentarer.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Bliv notificeret når andre brugere rapporterer problemer.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Bliv notificeret når problemer du har rapporteret er genåbnet.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Bliv notificeret når problemer du har rapporteret er løst.", + "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Bliv notificeret når andre brugere indsender nye medieforespørgsler som automatisk godkendes.", + "components.NotificationTypeSelector.usermediaapprovedDescription": "Bliv notificeret når dine medieforespørgsler godkendes.", + "components.NotificationTypeSelector.usermediaavailableDescription": "Bliv notificeret når dine medieforespørgsler bliver tilgængelige.", + "components.PermissionEdit.requestMoviesDescription": "Giv tilladelse til at indsende forespørgsler for ikke-4K film.", + "components.RequestModal.requestCancel": "Forespørgslen for {title} er annulleret.", + "components.RequestModal.requestSuccess": "{title} er blevet forespurgt!", + "components.RequestModal.requestadmin": "Din forespørgsel vil blive godkendt automatisk.", + "components.RequestModal.requestcancelled": "Forespørgslen for {title} er annulleret.", + "components.RequestModal.requestedited": "Forespørgslen for {title} er redigeret!", + "components.RequestModal.requesterror": "Noget gik galt under indsendelsen af forespørgslen.", + "components.RequestModal.requestfrom": "{username}s forespørgsel afventer godkendelse.", + "components.RequestModal.requestseasons": "Forespørg om {seasonCount} {seasonCount, plural, one {Sæson} other {Sæsoner}}", + "components.RequestModal.requesttitle": "Forespørg om {title}", + "components.RequestModal.season": "Sæson", + "components.ResetPassword.passwordreset": "Nulstil Kodeord", + "components.ResetPassword.requestresetlinksuccessmessage": "Et link til nulstilling af dit kodeord vil blive sendt til den angivne email adresse såfremt der eksisterer en konto til den.", + "components.ResetPassword.validationemailrequired": "Du skal angive en gyldig email adresse", + "components.ResetPassword.validationpasswordmatch": "Kodeordene skal være ens", + "components.ResetPassword.validationpasswordminchars": "Kodeordet er for kort; det skal være mindst 8 tegn", + "components.ResetPassword.validationpasswordrequired": "Du skal angive et kodeord", + "components.Search.search": "Søg", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Kun påkrævet hvis du benytter en anden profil end default", + "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "LunaSea testnotifikation kunne ikke afsendes.", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "Sender LunaSea testnotifikation…", + "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "Du skal angive en gyldig URL", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Din bruger- eller enhedsbaserede webhook URL for notifikationer", + "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Adgangstoken", + "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Opret en token fra dine Kontoindstillinger", + "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Aktivér Agent", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Sender Pushbullet testnotifikation…", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet testnotifikation er afsendt!", + "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "Du skal angive en adgangstoken", + "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Du skal vælge mindst én notifikationstype", + "components.Settings.Notifications.NotificationsPushover.accessToken": "Applikations API-token", + "components.Settings.Notifications.NotificationsPushover.agentenabled": "Aktivér Agent", + "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Sender Pushover testnotifikation…", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover testnotifikation er afsendt!", + "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sender web push testnotifikation…", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push testnotifikation kunne ikke sendes.", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push testnotifikation er afsendt!", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push notifikationdsindstillinger er blevet gemt!", + "components.Settings.Notifications.NotificationsWebhook.authheader": "Autorisations-header", + "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON indhold er blevet nulstillet!", + "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Hjælp til Skabelonsvariabler", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webbook testnotifikation kunne ikke afsendes.", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Sender webhook testnotifikation…", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook testnotifikation er afsendt!", + "components.NotificationTypeSelector.usermediadeclinedDescription": "Bliv notificeret når dine medieforespørgsler afvises.", + "components.RequestModal.autoapproval": "Automatisk Godkendelse", + "components.ResetPassword.emailresetlink": "Send email med gendannelseslink", + "components.NotificationTypeSelector.mediaAutoApproved": "Medie Automatisk Godkendt", + "components.NotificationTypeSelector.mediarequestedDescription": "Send notifikationer når brugere indsender nye medieforespørgsler som kræver godkendelse.", + "components.NotificationTypeSelector.notificationTypes": "Notifikationstyper", + "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Aktivér Agent", + "components.NotificationTypeSelector.mediaapproved": "Medie Godkendt", + "components.NotificationTypeSelector.issuecomment": "Problemkommentar", + "components.NotificationTypeSelector.issuecommentDescription": "Send notifikationer når problemer får nye kommentarer.", + "components.NotificationTypeSelector.issuecreated": "Problem Rapporteret", + "components.NotificationTypeSelector.issuecreatedDescription": "Send notifikationer når problemer rapporteres.", + "components.NotificationTypeSelector.issueresolved": "Problem Løst", + "components.NotificationTypeSelector.issueresolvedDescription": "Send notifikationer når problemer er løst.", + "components.NotificationTypeSelector.mediaapprovedDescription": "Send notifikationer når medieforespørgsler manuelt godkendes.", + "components.NotificationTypeSelector.mediaavailableDescription": "Send notifikationer når medieforespørgsler bliver tilgængelige.", + "components.NotificationTypeSelector.mediadeclined": "Medie Afvist", + "components.NotificationTypeSelector.usermediafailedDescription": "Bliv notificeret når medieforespørgsler ikke kunne tilføjes til Radarr eller Sonarr.", + "components.NotificationTypeSelector.usermediarequestedDescription": "Bliv notificeret når andre brugere indsender nye medieforespørgsler som kræver godkendelse.", + "components.PermissionEdit.autoapprove4kDescription": "Giv automatisk godkendelse for alle 4K medieforespørgsler.", + "components.PermissionEdit.autoapproveDescription": "Giv automatisk godkendelse for alle ikke-4K medieforespørgsler.", + "components.PermissionEdit.autoapproveSeriesDescription": "Giv automatisk godkendelse for ikke-4K serieforespørgsler.", + "components.PermissionEdit.createissues": "Rapportér Problemer", + "components.PermissionEdit.createissuesDescription": "Giv tilladelse til at rapportere medieproblemer.", + "components.PermissionEdit.manageissues": "Administrér Problemer", + "components.PermissionEdit.manageissuesDescription": "Giv tilladelse til at administrere medieproblemer.", + "components.RequestCard.failedretry": "Noget gik galt ved nyt forsøg på forespørgslen.", + "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {Ingen} other {#}} {type} {remaining, plural, one {forespørgsel} other {forespørgsler}} tilbage", + "components.RequestModal.SearchByNameModal.notvdbiddescription": "Vi kunne ikke automatisk matche din forespørgsel. Vælg venligst det korrekte match fra listen nedenfor.", + "components.RequestModal.pendingrequest": "Afventende Forespørgsel for {title}", + "components.RequestModal.request4ktitle": "Forespørg om {title} i 4K", + "components.Settings.Notifications.NotificationsPushover.userToken": "Bruger- eller Gruppenøgle", + "components.Settings.Notifications.NotificationsPushover.userTokenTip": "Dit 30-tegns bruger- eller gruppe-ID", + "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Du skal angive en gyldig bruger- eller gruppenøgle", + "components.Settings.Notifications.NotificationsSlack.agentenabled": "Aktivér Agent", + "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notificationsindstillinger er blevet gemt!", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Slack testnotifikation kunne ikke sendes.", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack testnotifikation er afsendt!", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSending": "Sender Slack testnotifikation…", + "components.Settings.Notifications.NotificationsSlack.validationTypes": "Du skal angive mindst én notifikationstype", + "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "Du skal angive et gyldigt URL", + "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "Du skal angive et gyldigt JSON indhold", + "components.Settings.Notifications.NotificationsWebhook.validationTypes": "Du skal vælge mindst én notifikationstype", + "components.Settings.Notifications.allowselfsigned": "Tillad selv-signerede certifikater", + "components.Settings.Notifications.authPass": "SMTP Kodeord", + "components.Settings.Notifications.botAvatarUrl": "Bot Avatar-URL", + "components.Settings.Notifications.botUsername": "Bot Brugernavn", + "components.Settings.Notifications.botUsernameTip": "Tillad brugere også at kunne starte en chat med din bot og konfigurere deres egne notifikationer", + "components.Settings.Notifications.encryptionTip": "I de fleste tilfælde bruger implicit TLC port 465 og STARTTLS port 587", + "components.Settings.Notifications.pgpPrivateKey": "PGP Privat Nøgle", + "components.Settings.Notifications.sendSilentlyTip": "Send notifikationer uden lyd", + "components.Settings.Notifications.smtpHost": "SMTP Vært", + "components.Settings.Notifications.senderName": "Afsendernavn", + "components.Settings.Notifications.toastDiscordTestFailed": "Discord testnotifikation kunne ikke sendes.", + "components.Settings.Notifications.validationSmtpHostRequired": "Du skal angive et gyldigt domænenavn eller en gyldig IP-adresse", + "components.Settings.Notifications.validationPgpPrivateKey": "Du skal angive en gyldig PGP privatnøgle", + "components.Settings.Notifications.validationUrl": "Du skal angive et gyldigt URL", + "components.Settings.Notifications.validationTypes": "Du skal angive mindst én notifikationstype", + "components.Settings.RadarrModal.add": "Tilføj Server", + "components.Settings.RadarrModal.apiKey": "API-nøgle", + "components.Settings.RadarrModal.loadingprofiles": "Indlæser kvalitetsprofiler…", + "components.Settings.RadarrModal.notagoptions": "Ingen tags.", + "components.Settings.RadarrModal.testFirstRootFolders": "Test forbindelse for at indlæse rodmapper", + "components.Settings.RadarrModal.testFirstQualityProfiles": "Test forbindelse for at indlæse kvalitetsprofiler", + "components.Settings.RadarrModal.validationApplicationUrl": "Du skal angive en gyldig URL", + "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL'er må ikke afsluttes med en skråstreg", + "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Base-URL'en må ikke afsluttes med en skråstreg", + "components.Settings.RadarrModal.validationHostnameRequired": "Du skal angive et gyldigt domænenavn eller en gyldig IP-adresse", + "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Base-URL'en må ikke starte med en skråstreg", + "components.Settings.RadarrModal.validationPortRequired": "Du skal angive et gyldigt port-nummer", + "components.Settings.RadarrModal.validationProfileRequired": "Du skal vælge en kvalitetsprofil", + "components.Settings.SettingsAbout.Releases.latestversion": "Nyeste", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Ændringer", + "components.Settings.SettingsAbout.Releases.releases": "Versioner", + "components.Settings.SettingsAbout.totalmedia": "Total Antal Medier", + "components.Settings.SettingsAbout.totalrequests": "Total Antal Forespørgsler", + "components.Settings.SettingsAbout.supportoverseerr": "Støt Overseerr", + "components.Settings.SettingsAbout.timezone": "Tidszone", + "components.Settings.SettingsAbout.uptodate": "Opdateret", + "components.Settings.SettingsAbout.version": "Version", + "components.Settings.SettingsJobsCache.cache": "Cache", + "components.Settings.SettingsJobsCache.cacheDescription": "Overseerr cacher forespørgsler til eksterne API-slutpunkter for at optimere ydeevne og undgå udnødvendige API kald.", + "components.Settings.SettingsJobsCache.cacheksize": "Nøglestørrelse", + "components.Settings.SettingsJobsCache.cachemisses": "Missere", + "components.Settings.RadarrModal.tags": "Tags", + "components.Settings.Notifications.chatId": "Chat-ID", + "components.Settings.Notifications.chatIdTip": "Start en chat med din bot, tilføj @get_id_bot og send kommandoen /my_id", + "components.Settings.Notifications.pgpPrivateKeyTip": "Signér krypterede emailbeskeder med OpenPGP", + "components.Settings.RadarrModal.loadingTags": "Indlæser tags…", + "components.Settings.RadarrModal.syncEnabled": "Aktivér Skanning", + "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Du skal vælge minimum tilgængelighed", + "components.Settings.RadarrModal.validationRootFolderRequired": "Du skal vælge en rodmappe", + "components.Settings.SettingsAbout.betawarning": "Dette er BETA software. Funktioner kan være i stykker og/eller ustabile. Rapportér gerne eventuelle problemer på GitHub!", + "components.Settings.RadarrModal.validationNameRequired": "Du skal angive et servernavn", + "components.Settings.SettingsJobsCache.command": "Kommando", + "components.Settings.SettingsJobsCache.download-sync-reset": "Nulstil Downloadsynkronisering", + "components.Settings.Notifications.botApiTip": "Opret en bot til brug med Overseerr", + "components.Settings.Notifications.discordsettingsfailed": "Discord notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.discordsettingssaved": "Discord notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.emailsender": "Afsenderadresse", + "components.Settings.Notifications.emailsettingsfailed": "Email notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.encryptionNone": "Ingen", + "components.Settings.Notifications.encryptionOpportunisticTls": "Brug altid STARTTLS", + "components.Settings.RadarrModal.selecttags": "Vælg tags", + "components.Settings.RadarrModal.server4k": "4K Server", + "components.Settings.RadarrModal.servername": "Servernavn", + "components.Settings.RadarrModal.ssl": "Brug SSL", + "components.Settings.RadarrModal.testFirstTags": "Test forbindelse for at indlæse tags", + "components.Settings.RadarrModal.toastRadarrTestFailure": "Kunne ikke forbinde til Radarr.", + "components.Settings.RadarrModal.toastRadarrTestSuccess": "Forbindelse til Radarr er oprettet!", + "components.Settings.RadarrModal.validationApiKeyRequired": "Du skal angive en API-nøgle", + "components.Settings.SettingsAbout.Releases.currentversion": "Nuværende", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Versionsdata er i øjeblikket ikke tilgængeligt.", + "components.Settings.SettingsAbout.Releases.viewchangelog": "Vis Ændringer", + "components.Settings.SettingsAbout.Releases.viewongithub": "Vis på GitHub", + "components.Settings.SettingsAbout.about": "Om", + "components.Settings.SettingsAbout.documentation": "Dokumentation", + "components.Settings.SettingsAbout.gettingsupport": "Få Hjælp", + "components.Settings.SettingsAbout.githubdiscussions": "GitHub Diskussioner", + "components.Settings.SettingsAbout.helppaycoffee": "Støt med en kop kaffe", + "components.Settings.SettingsAbout.overseerrinformation": "Om Overseerr", + "components.Settings.SettingsAbout.preferredmethod": "Foretrukket", + "components.Settings.SettingsAbout.runningDevelop": "Du anvender develop udgaven af Overseerr, som kun er anbefalet for dem der bidrager til udviklingen eller assisterer med testing af de nyeste funktioner.", + "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} cache er tømt.", + "components.Settings.SettingsJobsCache.cachehits": "Træffere", + "components.Settings.SettingsJobsCache.cachekeys": "Total antal nøgler", + "components.Settings.SettingsJobsCache.cachename": "Cache-navn", + "components.Settings.SettingsJobsCache.cachevsize": "Værdistørrelse", + "components.Settings.SettingsJobsCache.canceljob": "Annullér Job", + "components.Settings.SettingsJobsCache.download-sync": "Downloadsynkronisering", + "components.Settings.SettingsJobsCache.editJobSchedule": "Modificér Job", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frekvens", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Hvert {jobScheduleHours}. time", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Hvert {jobScheduleMinutes}. minut", + "components.Settings.Notifications.authUser": "SMTP Brugernavn", + "components.Settings.Notifications.botAPI": "Bot Autorisationstoken", + "components.Settings.Notifications.encryptionImplicitTls": "Brug implicit TLS", + "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram testnotifikation er blevet sendt!", + "components.Settings.Notifications.validationChatIdRequired": "Du skal angive et gyldigt chat-ID", + "components.Settings.Notifications.validationBotAPIRequired": "Du skal angive en bot autorisationstoken", + "components.Settings.RadarrModal.enableSearch": "Aktivér Automatisk Søgning", + "components.Settings.RadarrModal.selectQualityProfile": "Vælg kvalitetsprofil", + "components.Settings.RadarrModal.selectRootFolder": "Vælg rodmappe", + "components.Settings.SettingsAbout.outofdate": "Forældet", + "components.Settings.SettingsJobsCache.flushcache": "Tøm Cache", + "components.Settings.Notifications.emailsettingssaved": "Email notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.encryption": "Krypteringsmetode", + "components.Settings.Notifications.encryptionDefault": "Brug STARTTLS hvis tilgængeligt", + "components.Settings.Notifications.pgpPassword": "PGP Kodeord", + "components.Settings.Notifications.pgpPasswordTip": "Signér krypterede emailbeskeder med OpenPGP", + "components.Settings.Notifications.sendSilently": "Send lydløst", + "components.Settings.Notifications.smtpPort": "SMTP Port", + "components.Settings.Notifications.telegramsettingsfailed": "Telegram notifikationsindstillinger kunne ikke gemmes.", + "components.Settings.Notifications.telegramsettingssaved": "Telegram notifikationsindstillinger er blevet gemt!", + "components.Settings.Notifications.toastDiscordTestSending": "Sender Discord testnotifikation…", + "components.Settings.Notifications.toastDiscordTestSuccess": "Discord testnotifikation er blevet sendt!", + "components.Settings.Notifications.toastEmailTestFailed": "Email testnotifikation kunne ikke sendes.", + "components.Settings.Notifications.toastEmailTestSending": "Sender email testnotifikation…", + "components.Settings.Notifications.toastTelegramTestFailed": "Telegram testnotifikation kunne ikke sendes.", + "components.Settings.Notifications.toastTelegramTestSending": "Sender Telegram testnotifikation…", + "components.Settings.Notifications.toastEmailTestSuccess": "Email testnotifikation er blevet sendt!", + "components.Settings.Notifications.validationEmail": "Du skal angive en gyldig emailadresse", + "components.Settings.Notifications.validationPgpPassword": "Du skal angive et PGP kodeord", + "components.Settings.Notifications.validationSmtpPortRequired": "Du skal angive et gyldigt port-nummer", + "components.Settings.Notifications.webhookUrl": "Webhook URL", + "components.Settings.Notifications.webhookUrlTip": "Opret enwebhook integration til din server", + "components.Settings.RadarrModal.baseUrl": "URL Base", + "components.Settings.RadarrModal.create4kradarr": "Tilføj Ny 4K Radarr Server", + "components.Settings.RadarrModal.createradarr": "Tilføj Ny Radarr Server", + "components.Settings.RadarrModal.default4kserver": "Standard 4K Server", + "components.Settings.RadarrModal.defaultserver": "Standard Server", + "components.Settings.RadarrModal.edit4kradarr": "Redigér 4K Radarr Server", + "components.Settings.RadarrModal.editradarr": "Redigér Radarr Server", + "components.Settings.RadarrModal.externalUrl": "Ekstern URL", + "components.Settings.RadarrModal.hostname": "Domænenavn eller IP-adresse", + "components.Settings.RadarrModal.loadingrootfolders": "Indlæser rodmapper…", + "components.Settings.RadarrModal.minimumAvailability": "Minimum Tilgængelighed", + "components.Settings.RadarrModal.port": "Port", + "components.Settings.RadarrModal.qualityprofile": "Kvalitetsprofil", + "components.Settings.RadarrModal.rootfolder": "Rodmappe", + "components.Settings.RadarrModal.selectMinimumAvailability": "Vælg minimum tilgængelighed", + "components.Settings.is4k": "4K", + "components.Settings.SettingsJobsCache.unknownJob": "Ukendt Job", + "components.Settings.SonarrModal.edit4ksonarr": "Redigér 4K Sonarr Server", + "components.Settings.SonarrModal.editsonarr": "Redigér Sonarr Server", + "components.Settings.SonarrModal.notagoptions": "Ingen tags...", + "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Base URL'en skal have en indledende skråstreg", + "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL'en må ikke slutte med en skråstreg", + "components.Settings.activeProfile": "Aktiv Profil", + "components.Settings.cancelscan": "Annullér Skanning", + "components.Settings.default": "Standard", + "components.Settings.default4k": "Standard 4K", + "components.Settings.deleteserverconfirm": "Er du sikker på du vil slette denne server?", + "components.Settings.notificationsettings": "Notifikationsindstillinger", + "components.Settings.plexlibraries": "Plex Biblioteker", + "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Seneste tilføjet fra Plex-skanning", + "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} er annulleret.", + "components.Settings.SettingsJobsCache.jobtype": "Type", + "components.Settings.SettingsLogs.logs": "Logs", + "components.Settings.SonarrModal.syncEnabled": "Aktivér Skanning", + "components.Settings.SonarrModal.seasonfolders": "Sæsonmapper", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Noget gik galt i forsøget på at gemme jobbet.", + "components.Settings.SettingsJobsCache.jobname": "Jobnavn", + "components.Settings.SettingsJobsCache.jobs": "Jobs", + "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr udfører visse vedligeholdelsesopgaver som regelmæssige planlagte jobs men de kan også blive eksekveret manuelt nedenfor. En manuel eksekvering af et job vil ikke ændre på dens tidsplan.", + "components.Settings.SettingsLogs.logsDescription": "Du kan også se disse logs direkte fra stdout eller i {configDir}/logs/overseerr.log.", + "components.Settings.SettingsUsers.userSettingsDescription": "Konfigurér globale- og standardbrugerindstillinger.", + "components.Settings.SonarrModal.animeTags": "Anime Tags", + "components.Settings.SonarrModal.animelanguageprofile": "Anime Sprogprofil", + "components.Settings.SonarrModal.apiKey": "API-nøgle", + "components.Settings.SonarrModal.selectRootFolder": "Vælg rodmappe", + "components.Settings.SonarrModal.selecttags": "Vælg tags", + "components.Settings.SonarrModal.server4k": "4K Server", + "components.Settings.SonarrModal.servername": "Servernavn", + "components.Settings.SonarrModal.ssl": "Benyt SSL", + "components.Settings.SonarrModal.tags": "Tags", + "components.Settings.SonarrModal.testFirstLanguageProfiles": "Test forbindelsen for at indlæse sprogprofiler", + "components.Settings.SonarrModal.testFirstTags": "Test forbindelsen for at indlæse tags", + "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Base URL'en må ikke slutte med en skråstreg", + "components.Settings.SonarrModal.validationHostnameRequired": "Du skal angive et gyldigt domænenavn eller en gyldig IP adresse", + "components.Settings.SonarrModal.validationLanguageProfileRequired": "Du skal vælge en sprogprofil", + "components.Settings.SonarrModal.validationNameRequired": "Du skal angive et servernavn", + "components.Settings.SonarrModal.validationPortRequired": "Du skal angive et gyldigt port-nummer", + "components.Settings.SonarrModal.validationProfileRequired": "Du skal angive en kvalitetsprofil", + "components.Settings.SonarrModal.validationRootFolderRequired": "Du skal angive en rodmappe", + "components.Settings.address": "Adresse", + "components.Settings.addsonarr": "Tilføj Sonarr Server", + "components.Settings.apikey": "API-nøgle", + "components.Settings.applicationTitle": "Applikationstitel", + "components.Settings.applicationurl": "Applikations-URL", + "components.Settings.cacheImagesTip": "Optimér og gem alle billeder lokalt (anvender en betydelig mængde diskplads)", + "components.Settings.copied": "API-nøgle er kopieret til udklipsholder.", + "components.Settings.csrfProtection": "Aktivér CSRF Beskyttelse", + "components.Settings.csrfProtectionHoverTip": "Aktivér IKKE denne indstilling hvis ikke du forstår hvad du gør!", + "components.Settings.currentlibrary": "Nuværende Bibliotek: {name}", + "components.Settings.email": "Email", + "components.Settings.enablessl": "Benyt SSL", + "components.Settings.librariesRemaining": "Tilbageværende Biblioteker: {count}", + "components.Settings.manualscanDescription": "Normalt vil dette kun eksekveres én gang i døgnet. Overseerr vil tjekke din Plex-server's senest tilføjede medier mere aggressivt. Hvis dette er din første gang du konfigurerer Plex vil en enkelt manuel biblioteksskanning anbefales!", + "components.Settings.menuGeneralSettings": "Generelt", + "components.Settings.menuJobs": "Jobs & Cache", + "components.Settings.menuLogs": "Logs", + "components.Settings.menuNotifications": "Notifikationer", + "components.Settings.menuPlexSettings": "Plex", + "components.Settings.menuServices": "Tjenester", + "components.Settings.menuUsers": "Brugere", + "components.Settings.noDefault4kServer": "En 4K {serverType}server skal være markeret som standard for at aktivere muligheden for at brugere kan indsende 4K {mediaType}forespørgsler.", + "components.Settings.noDefaultNon4kServer": "Hvis du kun har en enkelt {serverType}server for både ikke-4K og 4K medier (eller du kun downloader 4K medier), skal din {serverType}server IKKE sættes som en 4K server.", + "components.Settings.noDefaultServer": "Mindst én {serverType}server skal markeres som standard for at {mediaType}forespørgsler kan afvikles.", + "components.Settings.notificationAgentSettingsDescription": "Konfigurér og aktivér notifikationsagenter.", + "components.Settings.notifications": "Notifikationer", + "components.Settings.originallanguage": "Udforsk Sprog", + "components.Settings.originallanguageTip": "Filtrer indhold efter originalsprog", + "components.Settings.partialRequestsEnabled": "Tillad Delvise Serieforespørgsler", + "components.Settings.plex": "Plex", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Jobbet er blevet redigeret!", + "components.Settings.csrfProtectionTip": "Sæt ekstern API-adgang til skrivebeskyttet (kræver HTTPS samt at Overseerr genstartes for at ændringerne vil træde i kraft)", + "components.Settings.generalsettingsDescription": "Konfigurér global- og standardindstillinger for Overseerr.", + "components.Settings.general": "Generelt", + "components.Settings.hideAvailable": "Skjul Tilgængelige Medier", + "components.Settings.hostname": "Domænenavn eller IP Adresse", + "components.Settings.generalsettings": "Generelle Indstillinger", + "components.Settings.menuAbout": "Om", + "components.Settings.SettingsLogs.label": "Label", + "components.Settings.SettingsLogs.level": "Alvorlighed", + "components.Settings.SettingsLogs.logDetails": "Loginformation", + "components.Settings.SonarrModal.baseUrl": "URL Base", + "components.Settings.SonarrModal.create4ksonarr": "Tilføj Ny 4K Sonarr Server", + "components.Settings.SonarrModal.loadingprofiles": "Indlæser kvalitetsprofiler…", + "components.Settings.cacheImages": "Aktivér billede-caching", + "components.Settings.locale": "Grænsefladesprog", + "components.Settings.manualscan": "Manuel Biblioteksskanning", + "components.Settings.mediaTypeMovie": "film", + "components.Settings.mediaTypeSeries": "serier", + "components.Settings.notrunning": "Stoppet", + "components.Settings.SettingsJobsCache.jobsandcache": "Jobs & Cache", + "components.Settings.SettingsUsers.newPlexLogin": "Aktiver Ny Plex Login", + "components.Settings.SettingsUsers.users": "Brugere", + "components.Settings.addradarr": "Tilføj Radarr Server", + "components.Settings.SettingsJobsCache.jobstarted": "{jobname} startet.", + "components.Settings.SettingsJobsCache.nextexecution": "Næste udførelse", + "components.Settings.SettingsJobsCache.plex-full-scan": "Fuld Plex Biblioteksskanning", + "components.Settings.SettingsJobsCache.process": "Proces", + "components.Settings.SettingsJobsCache.radarr-scan": "Radarr-skanning", + "components.Settings.SettingsJobsCache.runnow": "Eksekvér Nu", + "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr-skanning", + "components.Settings.SettingsLogs.copiedLogMessage": "Logbesked er kopieret til udklipsholder.", + "components.Settings.SettingsLogs.copyToClipboard": "Kopér til Udklipsholder", + "components.Settings.SettingsLogs.extraData": "Yderligere Data", + "components.Settings.SettingsLogs.filterDebug": "Fejlfinding", + "components.Settings.SettingsLogs.filterError": "Fejl", + "components.Settings.SettingsLogs.filterInfo": "Info", + "components.Settings.SettingsLogs.filterWarn": "Advarsel", + "components.Settings.SettingsLogs.message": "Besked", + "components.Settings.SettingsLogs.pauseLogs": "Pause", + "components.Settings.SettingsLogs.resumeLogs": "Fortsæt", + "components.Settings.SettingsLogs.showall": "Vis Alle Logs", + "components.Settings.SettingsLogs.time": "Tidsspunkt", + "components.Settings.SettingsUsers.defaultPermissions": "Standard Rettigheder", + "components.Settings.SettingsUsers.defaultPermissionsTip": "Initierende rettigheder som tildeles nye brugere", + "components.Settings.SettingsUsers.localLogin": "Aktivér Lokal Login", + "components.Settings.SettingsUsers.localLoginTip": "Tillad brugere at logge ind med deres email adresse og kodeord i stedet for Plex OAuth", + "components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Grænse For Filmforespørgsler", + "components.Settings.SettingsUsers.newPlexLoginTip": "Tillad Plex-brugere at logge ind uden først at være importeret", + "components.Settings.SettingsUsers.toastSettingsFailure": "Noget gik galt i forsøget på at gemme indstillingerne.", + "components.Settings.SettingsUsers.toastSettingsSuccess": "Brugerindstillingerne er blevet gemt!", + "components.Settings.SettingsUsers.tvRequestLimitLabel": "Global Grænse For Serieforespørgsler", + "components.Settings.SettingsUsers.userSettings": "Brugerindstillinger", + "components.Settings.SonarrModal.add": "Tilføj Server", + "components.Settings.SonarrModal.animequalityprofile": "Anime Kvalitetsprofil", + "components.Settings.SonarrModal.animerootfolder": "Anime Rodmappe", + "components.Settings.SonarrModal.createsonarr": "Tilføj Ny Sonarr Server", + "components.Settings.SonarrModal.default4kserver": "Standard 4K Server", + "components.Settings.SonarrModal.defaultserver": "Standard Server", + "components.Settings.SonarrModal.enableSearch": "Aktivér Automatisk Søgning", + "components.Settings.SonarrModal.externalUrl": "Ekstern URL", + "components.Settings.SonarrModal.loadingrootfolders": "Indlæser rodmapper…", + "components.Settings.SonarrModal.hostname": "Domænenavn eller IP adresse", + "components.Settings.SonarrModal.languageprofile": "Sprogprofil", + "components.Settings.SonarrModal.port": "Port", + "components.Settings.SonarrModal.loadingTags": "Indlæser tags…", + "components.Settings.SonarrModal.loadinglanguageprofiles": "Indlæser sprogprofiler…", + "components.Settings.SonarrModal.qualityprofile": "Kvalitetsprofil", + "components.Settings.SonarrModal.rootfolder": "Rodmappe", + "components.Settings.SonarrModal.selectLanguageProfile": "Vælg sprogprofil", + "components.Settings.SonarrModal.selectQualityProfile": "Vælg kvalitetsprofil", + "components.Settings.SonarrModal.testFirstQualityProfiles": "Test forbindelsen for at indlæse kvalitetsprofiler", + "components.Settings.SonarrModal.testFirstRootFolders": "Test forbindelsen for at indlæse rodmapper", + "components.Settings.SonarrModal.toastSonarrTestFailure": "Kunne ikke forbinde til Sonarr.", + "components.Settings.SonarrModal.toastSonarrTestSuccess": "Forbindelse til Sonarr er blevet etableret!", + "components.Settings.SonarrModal.validationApiKeyRequired": "Du skal angive en API-nøgle", + "components.Settings.SonarrModal.validationApplicationUrl": "Du skal angive en gyldig URL", + "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Ejer", + "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Notifikationsindstillinger", + "components.Settings.port": "Port", + "components.Settings.serverpreset": "Server", + "components.Settings.sonarrsettings": "Sonarr-indstillinger", + "components.Settings.ssl": "SSL", + "components.Settings.startscan": "Start Skanning", + "components.Settings.toastApiKeySuccess": "Ny API-nøgle er blevet genereret!", + "components.Settings.validationApplicationUrl": "Du skal angive en gyldig URL", + "components.Settings.validationWebAppUrl": "Du skal angive en gyldig Plex Web App-URL", + "components.Settings.webAppUrlTip": "Brugere dirigeres som alternativ til web-app'en på din server i stedet for den \"hostede\" web-app", + "components.Settings.webAppUrl": "Web-App-URL", + "components.Settings.webhook": "Webhook", + "components.StatusBadge.status4k": "4K {status}", + "components.Setup.tip": "Tip", + "components.Setup.welcome": "Velkommen til Overseerr", + "components.StatusChacker.reloadOverseerr": "Genindlæs", + "components.TvDetails.anime": "Anime", + "components.TvDetails.cast": "Roller", + "components.TvDetails.episodeRuntimeMinutes": "{runtime} minutter", + "components.TvDetails.originallanguage": "Originalsprog", + "components.TvDetails.originaltitle": "Originaltitel", + "components.UserList.importedfromplex": "{userCount, plural, one {# ny bruger} other {# nye brugere}} er blevet importeret fra Plex!", + "components.UserList.localLoginDisabled": "Indstillingen Aktivér Lokal Login er i øjeblikket deaktiveret.", + "components.UserList.validationpasswordminchars": "Kodeordet er for kort; det skal mindst bestå af 8 tegn", + "components.UserProfile.ProfileHeader.settings": "Redigér Indstillinger", + "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Kontotype", + "components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Kaldenavn", + "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Tilsidesæt Global Grænse", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filtrer indhold efter originalsprog", + "components.UserProfile.UserSettings.UserGeneralSettings.role": "Rolle", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Notifikationsindstillingerne for Discord er blevet gemt!", + "components.UserProfile.UserSettings.UserNotificationSettings.email": "Email", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Notifikationsindstillingerne for email er blevet gemt!", + "components.Settings.toastPlexConnecting": "Forsøger at forbinde itl Plex…", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Bruger- eller Gruppenøgle", + "components.Setup.signinMessage": "Kom i gang ved at logge ind med din Plex-konto", + "components.TvDetails.episodeRuntime": "Episode Spilletid", + "components.TvDetails.network": "{networkCount, plural, one {Netværk} other {Netværk}}", + "components.TvDetails.similar": "Lignende Serier", + "components.UserList.user": "Bruger", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Notifikationsindstillinger for Pushbullet kunne ikke gemmes.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Notifikationsindstillinger for Pushover kunne ikke gemmes.", + "components.Settings.plexlibrariesDescription": "Bibliotekerne Overseerr skanner for titler. Konfigurér og gem dine Plex-forbindelsesindstillinger og klik på knappen nedenfor hvis der ikke er vist nogle biblioteker.", + "components.Settings.plexsettingsDescription": "Konfigurér indstillingerne for din Plex server. Overseerr skanner dine Plex-biblioteker for at afgøre tilgængeligheden af indhold.", + "components.Settings.radarrsettings": "Radarr-indstillinger", + "components.Settings.region": "Udforsk Region", + "components.Settings.trustProxy": "Aktivér Proxy-understøttelse", + "components.Settings.trustProxyTip": "Tillad Overseerr at registrere klienters IP addresser korrekt bag en proxy (Overseerr skal genstartes for at ændringerne træder i kraft)", + "components.TvDetails.nextAirDate": "Næste Udsendelsesdato", + "components.TvDetails.playonplex": "Afspil i Plex", + "components.TvDetails.recommendations": "Anbefalinger", + "components.TvDetails.seasons": "{seasonCount, plural, one {# Sæson} other {# Sæsoner}}", + "components.TvDetails.streamingproviders": "Kan I Øjeblikket Streames På", + "components.TvDetails.viewfullcrew": "Vis Hele Rolleliste", + "components.TvDetails.watchtrailer": "Se Trailer", + "components.UserList.accounttype": "Type", + "components.UserList.admin": "Administrator", + "components.UserList.autogeneratepassword": "Generér Automatisk Kodeord", + "components.UserList.autogeneratepasswordTip": "Email et servergenereret kodeord til brugeren", + "components.UserList.bulkedit": "Masseredigering", + "components.UserList.create": "Opret", + "components.UserList.created": "Tilsluttet", + "components.UserList.createlocaluser": "Opret lokal bruger", + "components.UserList.creating": "Opretter…", + "components.UserList.importfromplexerror": "Noget gik galt under importeringen af brugere fra Plex.", + "components.UserList.sortRequests": "Antal Forespørgsler", + "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Grænse for Serieforespørgsler", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Indstillingerne er blevet gemt!", + "components.UserProfile.UserSettings.UserGeneralSettings.user": "Bruger", + "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "Bruger-ID", + "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "ID-nummeret for din brugerkonto", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Notifikationsindstillingerne for Discord kunne ikke gemmes.", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Kryptér emailbeskeder ved hjælp af OpenPGP", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Adgangstoken", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Opret en token fra dine kontoindstillinger", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Notifikationsindstillinger for Pushbullet er blevet gemt!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "API-token for applikationen", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registrér en applikation for brug med {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Dit 30-tegns bruger- eller gruppe-ID", + "components.Settings.scan": "Synkronisér Biblioteker", + "components.Settings.scanning": "Synkroniserer…", + "components.Settings.serverLocal": "lokal", + "components.Settings.serverRemote": "fjern", + "components.Settings.serverSecure": "sikker", + "components.Settings.serverpresetLoad": "Klik på knappen for at indlæse tilgængelige servere", + "components.Settings.services": "Tjenester", + "components.Settings.toastSettingsSuccess": "Indstillingerne er blevet gemt!", + "components.Settings.webpush": "Web Push", + "components.UserList.userlist": "Brugerliste", + "components.Settings.plexsettings": "Plex-indstillinger", + "components.TvDetails.TvCast.fullseriescast": "Rolleliste", + "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filtrér indhold efter regional tilgængelighed", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Noget gik galt under opdateringen af indstillingerne.", + "components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Notifikationer", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Notifikationsindstillingerne for email kunne ikke gemmes.", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "Offentlig PGP-nøgle", + "components.TvDetails.showtype": "Serietype", + "components.UserList.sortCreated": "Tilmeldingsdato", + "components.UserList.deleteconfirm": "Er du sikker på at du vil slette denne bruger? Alle deres forespørgselsdata vil blive slettet permanent.", + "components.Settings.regionTip": "Filtrer indhold efter regional tilgængelighed", + "components.Settings.serverpresetManualMessage": "Manuel konfiguration", + "components.Settings.serverpresetRefreshing": "Henter servere…", + "components.Settings.serviceSettingsDescription": "Konfigurér dine {serverType}server(e) nedenfor. Du kan forbinde til flere forskellige {serverType}servere men kun to af dem kan markeres som standard (én ikke-4K og én 4K). Administratorer kan ændre på serveren der bruges til at behandle nye forespørgsler inden godkendelse.", + "components.Settings.settingUpPlexDescription": "For at sætte Plex op skal du enten indtaste oplysningerne manuelt eller vælge en server som hentes fra plex.tv. Klik på knappen til højre for rullemenuen for at hente en liste af tilgængelige servere.", + "components.Settings.toastApiKeyFailure": "Noget gik galt under genereringen af en nye API-nøgle.", + "components.Settings.toastPlexConnectingFailure": "Kunne ikke forbinde til Plex.", + "components.Settings.toastPlexConnectingSuccess": "Plex forbindelse er etableret!", + "components.Settings.toastPlexRefresh": "Henter serverliste fra Plex…", + "components.Settings.toastPlexRefreshFailure": "Kunne ikke hente Plex-serverliste.", + "components.Settings.toastPlexRefreshSuccess": "Plex-serverliste er blevet hentet!", + "components.Settings.toastSettingsFailure": "Noget gik galt. Indstillingerne kunne ikke gemmes.", + "components.Settings.validationApplicationTitle": "Du skal angive en applikationstitel", + "components.Settings.validationApplicationUrlTrailingSlash": "URL'en må ikke afsluttes med en skråstreg", + "components.Settings.validationHostnameRequired": "Du skal angive et gyldigt domænenavn eller en gyldig IP adresse", + "components.Setup.configureplex": "Konfigurér Plex", + "components.Settings.validationPortRequired": "Du skal angive et gyldigt port-nummer", + "components.Setup.configureservices": "Konfigurér Tjenester", + "components.Setup.continue": "Fortsæt", + "components.Setup.finish": "Fuldfør Opsætning", + "components.Setup.finishing": "Færdiggører…", + "components.Setup.loginwithplex": "Log in med Plex", + "components.Setup.scanbackground": "Skanningen wil køre i baggrunden. Du kan fortsætte opsætningsprocessen i mellemtiden.", + "components.Setup.setup": "Opsætning", + "components.StatusChacker.newversionDescription": "Overseerr er blevet opdateret! Klik venligst på knappen nedenfor for at genindlæse siden.", + "components.TvDetails.firstAirDate": "Første Udsendelsesdato", + "components.TvDetails.overview": "Oversigt", + "components.StatusChacker.newversionavailable": "Applikationsopdatering", + "components.TvDetails.TvCrew.fullseriescrew": "Komplet Rolleliste", + "components.TvDetails.overviewunavailable": "Oversigt utilgængelig.", + "components.TvDetails.play4konplex": "Afspil i 4K i Plex", + "components.UserList.localuser": "Lokal Bruger", + "components.UserList.deleteuser": "Slet Bruger", + "components.UserList.displayName": "Kaldenavn", + "components.UserList.nouserstoimport": "Ingen nye brugere som kan importeres fra Plex.", + "components.UserList.edituser": "Redigér Brugertilladelser", + "components.UserList.email": "Email Adresse", + "components.UserList.importfromplex": "Importér Brugere fra Plex", + "components.UserList.owner": "Ejer", + "components.UserList.password": "Kodeord", + "components.UserList.passwordinfodescription": "Konfigurér en applikations-URL og aktivér emailnotifikationer for at tillade automatisk kodeordsgenerering.", + "components.UserList.plexuser": "Plex Bruger", + "components.UserList.role": "Rolle", + "components.UserList.sortDisplayName": "Kaldenavn", + "components.UserList.totalrequests": "Forespørgsler", + "components.UserList.usercreatedfailed": "Noget gik galt under oprettelsen af brugeren.", + "components.UserList.usercreatedfailedexisting": "Den angivne email adresse er allerede i brug af en anden bruger.", + "components.UserProfile.ProfileHeader.profile": "Vis Profil", + "components.UserProfile.ProfileHeader.userid": "Bruger-ID: {userid}", + "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Administrator", + "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Grænsefladesprog", + "components.UserProfile.UserSettings.UserGeneralSettings.general": "Generelt", + "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Generelle Indstillinger", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Standard ({language})", + "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Lokal Bruger", + "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Grænse for Filmforespørgsler", + "components.UserList.usercreatedsuccess": "Brugeren er blevet oprettet!", + "components.UserList.userdeleted": "Brugeren er blevet slettet!", + "components.UserList.userdeleteerror": "Noget gik galt under sletningen af brugeren.", + "components.UserList.userfail": "Noget gik galt under opdateringen af brugertilladelser.", + "components.UserList.users": "Brugere", + "components.UserList.userssaved": "Brugertilladelser er blevet gemt!", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Udforsk Sprog", + "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex-bruger", + "components.UserList.validationEmail": "Du skal angive en gyldig email adresse", + "components.UserProfile.ProfileHeader.joindate": "Tilsluttet {joindate}", + "components.UserProfile.UserSettings.UserGeneralSettings.region": "Udforsk region", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Notifikationsindstillinger for Pushover er blevet gemt!", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Notifikationsindstillingerne for Telegram er blevet gemt!", + "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Chat-ID", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Start en chat, tilføj @get_id_bot og benyt /my_id kommandoen", + "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Nuværende Kodeord", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Notifikationsindstillingerne for Telegram kunne ikke gemmes.", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Du skal angive en adgangstoken", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Du skal angive en gyldig applikationstoken", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Du skal angive en gyldig bruger- eller gruppenøgle", + "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Du skal angive et gyldigt chat-ID", + "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Du skal angive et bruger-ID", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Du skal angive en gyldig offentlig PGP-nøgle", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Notifikationsindstillingerne for web push kunne ikke gemmes.", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Notifikationsindstillingerne for web push er blevet gemt!", + "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Bekræft Kodeord", + "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "Nyt Kodeord", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Denne brugerkonto har i øjeblikket ikke et kodeord. Konfigurér et kodeord nedenfor så denne konto kan logge ind som en \"lokal bruger.\"", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Din konto har i øjeblikket ikke et kodeord. Konfigurér et kodeord nedenfor så du kan logge ind som \"lokal bruger\" med din email adresse.", + "i18n.next": "Næste", + "pages.oops": "Ups", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Send notifikationer uden lyd", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Kodeordet er blevet gemt!", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "Du skal bekræfte det nye kodeord", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Du skal angive et nyt kodeord", + "components.UserProfile.UserSettings.UserPermissions.permissions": "Tilladelser", + "components.UserProfile.UserSettings.menuGeneralSettings": "Generelt", + "components.UserProfile.UserSettings.menuNotifications": "Notifikationer", + "components.UserProfile.movierequests": "Filmforespørgsler", + "components.UserProfile.limit": "{remaining} ud af {limit}", + "i18n.available": "Tilgængelig", + "i18n.experimental": "Eksperimentel", + "i18n.delimitedlist": "{a}, {b}", + "i18n.edit": "Redigér", + "i18n.failed": "Fejlet", + "i18n.noresults": "Ingen resultater.", + "i18n.notrequested": "Ikke Forespurgt", + "i18n.settings": "Indstillinger", + "i18n.usersettings": "Brugerindstillinger", + "i18n.tvshows": "Serier", + "i18n.unavailable": "Utilgængelig", + "i18n.view": "Vis", + "components.UserProfile.unlimited": "Ubegrænset", + "components.UserProfile.UserSettings.menuChangePass": "Kodeord", + "i18n.request": "Forespørg", + "i18n.processing": "Behandler", + "i18n.request4k": "Forespørg i 4K", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Send Lydløst", + "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "Du har ikke tilladelse til at ændre denne bruger's kodeord.", + "components.UserProfile.UserSettings.UserPasswordChange.password": "Kodeord", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Noget gik galt, kodeordet kunne ikke gemmes.", + "components.UserProfile.UserSettings.menuPermissions": "Tilladelser", + "components.UserProfile.UserSettings.unauthorizedDescription": "Du har ikke tilladelse til at ændre denne bruger's indstillinger.", + "components.UserProfile.norequests": "Ingen forespørgsler.", + "components.UserProfile.pastdays": "{type} (seneste {days} dage)", + "components.UserProfile.recentrequests": "Seneste Forespørgsler", + "components.UserProfile.requestsperdays": "{limit} tilbage", + "components.UserProfile.totalrequests": "Antal Forespørgsler", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Noget gik galt, kodeordet kunne ikke gemmes. Havde du indtastet dit nuværende kodeord korrekt?", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPasswordSame": "Kodeordene skal være ens", + "components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "Du skal angive dit nuværende kodeord", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPasswordLength": "Kodeordet er for kort; det skal bestå af mindst 8 tegn", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Noget gik galt da indstillingerne skulle gemmes.", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "Tilladelserne er blevet gemt!", + "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Du kan ikke ændre dine egne tilladelser.", + "components.UserProfile.seriesrequest": "Serieforespørgsler", + "i18n.declined": "Afvist", + "i18n.delete": "Slet", + "i18n.deleting": "Sletter…", + "i18n.loading": "Indlæser…", + "i18n.movie": "Film", + "i18n.movies": "Film", + "i18n.open": "Åbn", + "i18n.partiallyavailable": "Delvist Tilgængelig", + "i18n.pending": "Afventer", + "i18n.requesting": "Forespørger…", + "i18n.resolved": "Løst", + "i18n.resultsperpage": "Vis {pageSize} resultater pr. side", + "i18n.retry": "Forsøg igen", + "i18n.retrying": "Forsøger igen…", + "i18n.save": "Gem Ændringer", + "i18n.previous": "Forrige", + "i18n.approve": "Godkend", + "i18n.approved": "Godkendt", + "i18n.areyousure": "Er du sikker?", + "i18n.back": "Tilbage", + "i18n.cancel": "Annullér", + "i18n.canceling": "Annullerer…", + "i18n.close": "Luk", + "i18n.decline": "Afvis", + "i18n.showingresults": "Viser {from} til {to} af {total} resultater", + "i18n.status": "Status", + "i18n.testing": "Tester…", + "i18n.tvshow": "Serie", + "pages.errormessagewithcode": "{statusCode} - {error}", + "pages.internalservererror": "Intern serverfejl", + "pages.pagenotfound": "Siden Kunne Ikke Findes", + "pages.returnHome": "Gå tilbage til startsiden", + "i18n.all": "Alle", + "i18n.advanced": "Avanceret", + "i18n.requested": "Forespurgt", + "pages.serviceunavailable": "Service Utilgængelig", + "pages.somethingwentwrong": "Noget Gik Galt", + "i18n.saving": "Gemmer…", + "i18n.test": "Test" } diff --git a/src/i18n/locale/de.json b/src/i18n/locale/de.json index fb29bcea7..2aad43c73 100644 --- a/src/i18n/locale/de.json +++ b/src/i18n/locale/de.json @@ -16,16 +16,11 @@ "components.Layout.UserDropdown.signout": "Abmelden", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cast": "Besetzung", - "components.MovieDetails.manageModalClearMedia": "Mediendaten löschen", - "components.MovieDetails.manageModalClearMediaWarning": "* Dies wird unwiederbringlich alle Daten zu diesem Film löschen, inklusive jeglicher Anfragen. Falls dieses Element in deiner Plex-Bibliothek existiert, werden die Medieninformationen beim nächsten Scannen neu erstellt.", - "components.MovieDetails.manageModalNoRequests": "Keine Anfragen.", - "components.MovieDetails.manageModalRequests": "Anfragen", - "components.MovieDetails.manageModalTitle": "Film verwalten", "components.MovieDetails.originallanguage": "Originalsprache", "components.MovieDetails.overview": "Übersicht", "components.MovieDetails.overviewunavailable": "Übersicht nicht verfügbar.", "components.MovieDetails.recommendations": "Empfehlungen", - "components.MovieDetails.releasedate": "{releaseCount, plural, one {Erscheinungsdatum} other {Erscheinungsdaten}}", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Release Date} other {Release Dates}}", "components.MovieDetails.revenue": "Einnahmen", "components.MovieDetails.runtime": "{minutes} Minuten", "components.MovieDetails.similar": "Ähnliche Titel", @@ -148,19 +143,13 @@ "components.Setup.signinMessage": "Melde dich zunächst mit deinem Plex-Konto an", "components.Setup.welcome": "Willkommen bei Overseerr", "components.TvDetails.cast": "Besetzung", - "components.TvDetails.manageModalClearMedia": "Mediendaten löschen", - "components.TvDetails.manageModalClearMediaWarning": "* Dies wird unwiederbringlich alle Daten zu dieser Serie löschen, inklusive jeglicher Anfrage dafür. Falls dieses Element in deiner Plex-Bibliothek existiert, werden die Medieninformationen beim nächsten Scannen neu erstellt.", - "components.TvDetails.manageModalNoRequests": "Keine Anfragen.", - "components.TvDetails.manageModalRequests": "Anfragen", - "components.TvDetails.manageModalTitle": "Serie verwalten", "components.TvDetails.originallanguage": "Originalsprache", "components.TvDetails.overview": "Übersicht", "components.TvDetails.overviewunavailable": "Übersicht nicht verfügbar.", "components.TvDetails.recommendations": "Empfehlungen", "components.TvDetails.similar": "Ähnliche Serien", "components.UserList.admin": "Admin", - "components.UserList.created": "Erstellt", - "components.UserList.lastupdated": "Aktualisiert", + "components.UserList.created": "Beigetreten", "components.UserList.plexuser": "Plex-Benutzer", "components.UserList.role": "Rolle", "components.UserList.totalrequests": "Anfragen", @@ -193,7 +182,7 @@ "components.Settings.SettingsAbout.version": "Version", "components.Settings.SettingsAbout.totalrequests": "Anfragen insgesamt", "components.Settings.SettingsAbout.totalmedia": "Medien insgesamt", - "components.Settings.SettingsAbout.overseerrinformation": "Overseerr-Informationen", + "components.Settings.SettingsAbout.overseerrinformation": "Über Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "GitHub-Diskussionen", "components.Settings.SettingsAbout.gettingsupport": "Hilfe erhalten", "components.Settings.RadarrModal.validationNameRequired": "Du musst einen Servernamen angeben", @@ -228,11 +217,11 @@ "components.Settings.SettingsAbout.helppaycoffee": "Hilf uns Kaffee zu bezahlen", "components.Settings.SettingsAbout.Releases.viewongithub": "Auf GitHub anzeigen", "components.Settings.SettingsAbout.Releases.viewchangelog": "Änderungsprotokoll anzeigen", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Änderungsprotokoll", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Änderungsprotokoll {version}", "components.Settings.SettingsAbout.Releases.releases": "Veröffentlichungen", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informationen der Version nicht verfügbar. Ist GitHub offline?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informationen der Version nicht verfügbar.", "components.Settings.SettingsAbout.Releases.latestversion": "Neuste", - "components.Settings.SettingsAbout.Releases.currentversion": "Aktuelle Version", + "components.Settings.SettingsAbout.Releases.currentversion": "Aktuell", "components.UserList.importfromplexerror": "Beim Importieren von Benutzern aus Plex ist etwas schief gelaufen.", "components.UserList.importfromplex": "Benutzer aus Plex importieren", "components.TvDetails.viewfullcrew": "Komplette Crew anzeigen", @@ -245,9 +234,7 @@ "components.Settings.Notifications.allowselfsigned": "Selbstsignierte Zertifikate erlauben", "components.TvDetails.watchtrailer": "Trailer ansehen", "components.MovieDetails.watchtrailer": "Trailer ansehen", - "components.CollectionDetails.requestswillbecreated": "Für die folgenden Titel werden Anfragen erstellt:", "components.CollectionDetails.requestcollection": "Sammlung anfragen", - "components.CollectionDetails.requestSuccess": "{title} erfolgreich angefragt!", "components.CollectionDetails.overview": "Übersicht", "components.CollectionDetails.numberofmovies": "{count} Filme", "i18n.requested": "Angefragt", @@ -286,7 +273,7 @@ "components.Settings.Notifications.NotificationsPushover.agentenabled": "Agent aktivieren", "components.Settings.Notifications.NotificationsPushover.accessToken": "Anwendungs API-Token", "components.RequestList.sortModified": "Zuletzt geändert", - "components.RequestList.sortAdded": "Anfragedatum", + "components.RequestList.sortAdded": "Zuletzt angefragt", "components.RequestList.showallrequests": "Zeige alle Anfragen", "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook-Benachrichtigungseinstellungen konnten nicht gespeichert werden.", "components.StatusBadge.status4k": "4K {status}", @@ -367,7 +354,7 @@ "components.PermissionEdit.request": "Anfrage", "components.PermissionEdit.autoapproveMovies": "Automatische Genehmigung von Filmen", "components.PermissionEdit.admin": "Admin", - "components.PermissionEdit.managerequestsDescription": "Gewähre Berechtigung zum Verwalten von Overseerr-Anfragen. Alle Anfragen eines Benutzers mit dieser Berechtigung werden automatisch genehmigt.", + "components.PermissionEdit.managerequestsDescription": "Gewähre Berechtigung zum Verwalten von Medienanfragen. Alle Anfragen eines Benutzers mit dieser Berechtigung werden automatisch genehmigt.", "components.UserList.userssaved": "Benutzerberechtigungen erfolgreich gespeichert!", "components.UserList.bulkedit": "Ausgewählte bearbeiten", "components.Settings.toastPlexRefreshSuccess": "Plex-Serverliste erfolgreich abgerufen!", @@ -387,39 +374,30 @@ "components.Settings.csrfProtection": "Aktiviere CSRF Schutz", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Sonarr-Verbindung erfolgreich hergestellt!", "components.Settings.SonarrModal.toastSonarrTestFailure": "Verbindung zu Sonarr fehlgeschlagen.", - "components.PermissionEdit.usersDescription": "Gewähre Berechtigung zum Verwalten von Overseerr-Benutzern. Benutzer mit dieser Berechtigung können Benutzer mit Adminrechten nicht bearbeiten oder Adminrechte erteilen.", + "components.PermissionEdit.usersDescription": "Gewähre Berechtigung zum Verwalten von Benutzern. Benutzer mit dieser Berechtigung können Benutzer mit Adminrechten nicht bearbeiten oder Adminrechte erteilen.", "components.PermissionEdit.users": "Benutzer verwalten", - "components.PermissionEdit.settingsDescription": "Gewähre Berechtigung zum ändern von Overseerr-Einstellungen. Ein Benutzer muss über diese Berechtigung verfügen, um sie anderen Benutzern erteilen zu können.", + "components.PermissionEdit.settingsDescription": "Gewähre Berechtigung um globale Einstellungen zu ändern. Ein Benutzer muss über diese Berechtigung verfügen, um sie anderen Benutzern erteilen zu können.", "components.PermissionEdit.settings": "Einstellungen verwalten", - "components.PermissionEdit.requestDescription": "Gewähre Berechtigung zum Anfragen von nicht 4K Medien.", - "components.PermissionEdit.request4kTvDescription": "Gewähre Berechtigung Serien in 4K anzufragen.", + "components.PermissionEdit.requestDescription": "Berechtigt, nicht-4K Inhalte anzufragen.", + "components.PermissionEdit.request4kTvDescription": "Berechtigt, Serien in 4K anzufragen.", "components.PermissionEdit.request4kTv": "4K Serien anfragen", - "components.PermissionEdit.request4kMoviesDescription": "Gewähre Berechtigung Filme in 4K anzufragen.", + "components.PermissionEdit.request4kMoviesDescription": "Berechtigt, Filme in 4K anzufragen.", "components.PermissionEdit.request4kMovies": "4K Filme anfragen", "components.PermissionEdit.request4k": "4K anfragen", "components.PermissionEdit.request4kDescription": "Gewähre Berechtigung Medien in 4K anzufragen.", "components.PermissionEdit.autoapproveSeriesDescription": "Gewähre Berechtigung zur automatischen Genehmigung von nicht-4K Serienanfragen.", "components.PermissionEdit.autoapproveMoviesDescription": "Gewähre Berechtigung zur automatischen Genehmigung von nicht-4K Filmanfragen.", "components.PermissionEdit.autoapproveDescription": "Gewähre Berechtigung zur automatischen Genehmigung von allen nicht-4K Anfragen.", - "components.PermissionEdit.advancedrequestDescription": "Gewähre Berechtigung zum Verwenden erweiterter Anfrageoptionen.", + "components.PermissionEdit.advancedrequestDescription": "Gewähre Berechtigung zum Ändern erweiterter Anfrageoptionen.", "components.PermissionEdit.adminDescription": "Voller Administratorzugriff. Umgeht alle anderen Rechteabfragen.", - "components.MovieDetails.openradarr4k": "Film in 4K Radarr öffnen", - "components.MovieDetails.openradarr": "Film in Radarr öffnen", - "components.TvDetails.opensonarr4k": "Serie in 4K Sonarr öffnen", - "components.TvDetails.opensonarr": "Serie in Sonarr öffnen", - "components.TvDetails.downloadstatus": "Herunterladen-Status", "components.Settings.SonarrModal.syncEnabled": "Scannen aktivieren", "components.Settings.SonarrModal.externalUrl": "Externe URL", "components.Settings.RadarrModal.syncEnabled": "Scannen aktivieren", "components.Settings.RadarrModal.externalUrl": "Externe URL", - "components.MovieDetails.downloadstatus": "Herunterladen-Status", "components.TvDetails.playonplex": "Auf Plex abspielen", "components.MovieDetails.playonplex": "Auf Plex abspielen", "components.TvDetails.play4konplex": "In 4K auf Plex abspielen", "components.MovieDetails.play4konplex": "In 4K auf Plex abspielen", - "components.TvDetails.markavailable": "Als verfügbar markieren", - "components.TvDetails.mark4kavailable": "4K als verfügbar markieren", - "components.TvDetails.allseasonsmarkedavailable": "* Alle Staffeln werden als verfügbar markiert.", "components.Settings.trustProxyTip": "Erlaubt es Overseerr Client IP Adressen hinter einem Proxy korrekt zu registrieren (Overseerr muss neu gestartet werden, damit die Änderungen wirksam werden)", "components.Settings.trustProxy": "Proxy-Unterstützung aktivieren", "components.Settings.SettingsJobsCache.runnow": "Jetzt ausführen", @@ -474,7 +452,7 @@ "components.ResetPassword.gobacklogin": "Zurück zur Anmeldeseite", "components.ResetPassword.emailresetlink": "Wiederherstellungs-Link per E-Mail senden", "components.ResetPassword.email": "E-Mail-Adresse", - "components.PermissionEdit.viewrequestsDescription": "Gewähre Berechtigung zum Anzeigen von Anfragen anderer Benutzer.", + "components.PermissionEdit.viewrequestsDescription": "Berechtigt, Anfragen anderer Nutzer zu sehen.", "components.PermissionEdit.viewrequests": "Anfragen anzeigen", "components.Login.forgotpassword": "Passwort vergessen?", "components.Settings.csrfProtectionHoverTip": "Aktiviere diese Option NICHT, es sei denn du weißt, was du tust!", @@ -497,9 +475,8 @@ "components.Settings.Notifications.sendSilentlyTip": "Sende Benachrichtigungen ohne Ton", "components.UserList.sortRequests": "Anzahl der Anfragen", "components.UserList.sortDisplayName": "Anzeigename", - "components.UserList.sortCreated": "Erstellungsdatum", + "components.UserList.sortCreated": "Beitrittsdatum", "components.PermissionEdit.autoapprove4k": "Automatische Genehmigung von 4K", - "components.UserList.sortUpdated": "Zuletzt aktualisiert", "components.PermissionEdit.autoapprove4kSeriesDescription": "Gewähre Berechtigung zur automatischen Genehmigung von 4K Serienanfragen.", "components.PermissionEdit.autoapprove4kSeries": "Automatische Genehmigung von 4K Serien", "components.PermissionEdit.autoapprove4kMoviesDescription": "Gewähre Berechtigung zur automatischen Genehmigung von 4K Filmanfragen.", @@ -545,7 +522,6 @@ "components.Layout.UserDropdown.myprofile": "Profil", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Du musst eine gültige Benutzer-ID angeben", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "Die ID Nummer für dein Benutzerkonto", - "components.CollectionDetails.requestswillbecreated4k": "Für die folgenden Titel werden 4K Anfragen erstellt:", "components.CollectionDetails.requestcollection4k": "Sammlung in 4K anfragen", "components.Settings.region": "Region Entdecken", "components.Settings.originallanguage": "Sprache Entdecken", @@ -618,7 +594,6 @@ "components.Settings.partialRequestsEnabled": "Teilserienanfragen erlauben", "components.Settings.Notifications.pgpPrivateKey": "PGP Privater Schlüssel", "components.Settings.Notifications.pgpPassword": "PGP Passwort", - "components.RequestModal.requestall": "Alle Jahreszeiten Anfordern", "components.RequestModal.alreadyrequested": "Bereits Angefragt", "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Sende eine Benachrichtigung, wenn das angeforderte Medium automatisch genehmigt wird.", @@ -801,7 +776,7 @@ "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Registriere eine Anwendung für die Benutzung mit Overseerr", "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Sie müssen mindestens einen Benachrichtigungstypen auswählen", "components.RequestCard.failedretry": "Beim erneuten Versuch die Anfrage zu senden ist ein Fehler aufgetreten.", - "components.PermissionEdit.requestTvDescription": "Berechtigungen erteilen um nicht 4K Serien anzufragen.", + "components.PermissionEdit.requestTvDescription": "Berechtigt, nicht-4K Serien anzufragen.", "components.NotificationTypeSelector.usermediafailedDescription": "Werde benachrichtigt, wenn die angeforderten Medien bei der Hinzufügung zu Radarr oder Sonarr fehlschlagen.", "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Erstellen Sie einen Token in Ihren Account Einstellungen", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL", @@ -813,9 +788,9 @@ "components.RequestList.RequestItem.requesteddate": "Angefordert", "components.QuotaSelector.tvRequests": "{quotaLimit} {seasons} pro {quotaDays} {days}", "components.QuotaSelector.movieRequests": "{quotaLimit} {movies} pro {quotaDays} {days}", - "components.PermissionEdit.requestTv": "Serie anfordern", - "components.PermissionEdit.requestMoviesDescription": "Berechtigungen erteilen um nicht 4K Filme anzufordern.", - "components.PermissionEdit.requestMovies": "Filme Anfragen", + "components.PermissionEdit.requestTv": "Serien anfragen", + "components.PermissionEdit.requestMoviesDescription": "Berechtigt, nicht-4K Filme anzufordern.", + "components.PermissionEdit.requestMovies": "Filme anfragen", "components.NotificationTypeSelector.usermediarequestedDescription": "Werde benachrichtigt, wenn andere Nutzer ein Medium anfordern, welches eine Genehmigung erfordert.", "components.NotificationTypeSelector.usermediadeclinedDescription": "Werde benachrichtigt, wenn deine Medienanfrage abgelehnt wurde.", "components.NotificationTypeSelector.usermediaavailableDescription": "Werde benachrichtigt, wenn deine Medienanfrage verfügbar ist.", @@ -881,5 +856,130 @@ "components.MovieDetails.showmore": "Mehr Anzeigen", "components.MovieDetails.streamingproviders": "Streamt derzeit auf", "components.TvDetails.streamingproviders": "Streamt derzeit auf", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.reopenissueandcomment": "Mit Kommentar wieder öffnen", + "components.IssueModal.CreateIssueModal.allseasons": "Alle Staffeln", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Gibt es ein Problem mit {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Betroffene Episode", + "components.IssueModal.CreateIssueModal.problemseason": "Betroffene Staffel", + "components.IssueModal.CreateIssueModal.providedetail": "Geben Sie eine detaillierte Erklärung des Problems an.", + "components.IssueModal.CreateIssueModal.reportissue": "Ein Problem melden", + "components.IssueDetails.IssueComment.areyousuredelete": "Möchten Sie diesen Kommentar wirklich löschen?", + "components.IssueDetails.IssueComment.delete": "Kommentar löschen", + "components.IssueDetails.IssueComment.edit": "Kommentar bearbeiten", + "components.IssueDetails.IssueComment.postedby": "Gepostet {relativeTime} von {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Gepostet {relativeTime} von {username} (Bearbeitet)", + "components.IssueDetails.IssueComment.validationComment": "Sie müssen eine Nachricht eingeben", + "components.IssueDetails.IssueDescription.deleteissue": "Problem löschen", + "components.IssueDetails.toasteditdescriptionsuccess": "Problembeschreibung erfolgreich bearbeitet!", + "components.IssueDetails.toastissuedeleted": "Problem erfolgreich gelöscht!", + "components.IssueDetails.toasteditdescriptionfailed": "Beim Bearbeiten der Problembeschreibung ist ein Fehler aufgetreten.", + "components.IssueDetails.IssueDescription.description": "Beschreibung", + "components.IssueDetails.IssueDescription.edit": "Beschreibung bearbeiten", + "components.IssueDetails.allepisodes": "Alle Folgen", + "components.IssueDetails.allseasons": "Alle Staffeln", + "components.IssueDetails.closeissue": "Problem schließen", + "components.IssueDetails.closeissueandcomment": "Schließen mit Kommentar", + "components.IssueDetails.comments": "Kommentare", + "components.IssueDetails.deleteissue": "Problem löschen", + "components.IssueDetails.deleteissueconfirm": "Möchten Sie dieses Problem wirklich löschen?", + "components.IssueDetails.episode": "Folge {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Problem", + "components.IssueDetails.issuetype": "Typ", + "components.IssueDetails.lastupdated": "Letzte Aktualisierung", + "components.IssueDetails.leavecomment": "Kommentar", + "components.IssueDetails.openinarr": "In {arr} öffnen", + "components.IssueDetails.toastissuedeletefailed": "Beim Löschen des Problems ist ein Fehler aufgetreten.", + "components.IssueDetails.toaststatusupdatefailed": "Beim Aktualisieren des Problemstatus ist ein Fehler aufgetreten.", + "components.IssueDetails.unknownissuetype": "Unbekannt", + "components.IssueList.IssueItem.issuetype": "Typ", + "components.IssueList.IssueItem.openeduserdate": "{date} von {user}", + "components.IssueList.IssueItem.problemepisode": "Betroffene Episode", + "components.IssueList.IssueItem.unknownissuetype": "Unbekannt", + "components.IssueList.showallissues": "Alle Probleme anzeigen", + "components.IssueList.sortAdded": "Neueste", + "components.IssueList.sortModified": "Zuletzt geändert", + "components.IssueDetails.nocomments": "Keine Kommentare.", + "components.IssueDetails.openedby": "#{issueId} geöffnet {relativeTime} von {username}", + "components.IssueDetails.openin4karr": "In {arr} 4K öffnen", + "components.IssueDetails.play4konplex": "Auf Plex in 4K abspielen", + "components.IssueDetails.playonplex": "Auf Plex abspielen", + "components.IssueDetails.problemepisode": "Betroffene Episode", + "components.IssueDetails.problemseason": "Betroffene Staffeln", + "components.IssueDetails.reopenissue": "Problem erneut öffnen", + "components.IssueDetails.season": "Staffel {seasonNumber}", + "components.IssueDetails.toaststatusupdated": "Ausgabestatus erfolgreich aktualisiert!", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.opened": "Geöffnet", + "components.IssueList.IssueItem.viewissue": "Problem anzeigen", + "components.IssueModal.CreateIssueModal.allepisodes": "Alle Folgen", + "components.IssueModal.CreateIssueModal.season": "Staffel {seasonNumber}", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Beim Senden des Problems ist ein Fehler aufgetreten.", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Häufigkeit", + "components.Settings.SettingsJobsCache.editJobSchedule": "Job ändern", + "components.NotificationTypeSelector.userissuecommentDescription": "Lassen Sie sich benachrichtigen, wenn Ihre Probleme neue Kommentare erhalten.", + "components.NotificationTypeSelector.issuecomment": "Problem Kommentar", + "i18n.open": "Offen", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Die Einstellungen für Pushbullet-Benachrichtigungen konnten nicht gespeichert werden.", + "components.IssueModal.CreateIssueModal.submitissue": "Problem einreichen", + "components.IssueModal.issueAudio": "Ton", + "components.IssueModal.issueSubtitles": "Untertitel", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Erstellen Sie ein Token aus Ihren Kontoeinstellungen.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Zugangs-Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet-Benachrichtigungseinstellungen erfolgreich gespeichert!", + "components.IssueModal.CreateIssueModal.extras": "Extras", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Du musst eine Beschreibung liefern", + "components.IssueModal.CreateIssueModal.whatswrong": "Was ist los?", + "components.IssueModal.issueOther": "Andere", + "components.Layout.Sidebar.issues": "Probleme", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Alle Staffeln werden als verfügbar gekennzeichnet.", + "components.ManageSlideOver.downloadstatus": "Download Status", + "components.ManageSlideOver.manageModalClearMedia": "Löschen der Mediendaten", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Dadurch werden alle Daten für diesen {mediaType} unwiderruflich entfernt, einschließlich aller Anfragen. Wenn dieses Element in Ihrer Plex-Bibliothek existiert, werden die Medieninformationen beim nächsten Scan neu erstellt.", + "components.ManageSlideOver.manageModalIssues": "Problem öffnen", + "components.ManageSlideOver.manageModalNoRequests": "Keine Anfragen.", + "components.ManageSlideOver.manageModalRequests": "Anfragen", + "components.ManageSlideOver.manageModalTitle": "{mediaType} verwalten", + "components.ManageSlideOver.mark4kavailable": "Als in 4K verfügbar markieren", + "components.ManageSlideOver.markavailable": "Als verfügbar markieren", + "components.ManageSlideOver.movie": "Film", + "components.ManageSlideOver.openarr": "Öffnen in {arr}", + "components.ManageSlideOver.openarr4k": "Öffnen in 4K {arr}", + "components.ManageSlideOver.tvshow": "Serie", + "components.NotificationTypeSelector.issuecreatedDescription": "Senden Sie Benachrichtigungen, wenn Probleme gemeldet werden.", + "components.NotificationTypeSelector.issueresolved": "Problem gelöst", + "components.NotificationTypeSelector.issueresolvedDescription": "Senden Sie Benachrichtigungen, wenn Probleme gelöst sind.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Lassen Sie sich benachrichtigen, wenn andere Benutzer Probleme melden.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Lassen Sie sich benachrichtigen, wenn Ihre Probleme gelöst sind.", + "components.PermissionEdit.createissues": "Probleme melden", + "components.Settings.SettingsAbout.runningDevelop": "Sie benutzen den develop von Overseerr, der nur für diejenigen empfohlen wird, die an der Entwicklung mitwirken oder bei den neuesten Tests helfen.", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Alle {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Alle {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Beim Speichern des Auftrags ging etwas schief.", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Auftrag erfolgreich bearbeitet!", + "components.IssueList.issues": "Probleme", + "components.IssueModal.CreateIssueModal.episode": "Folgen {episodeNumber}", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Problembericht für {title} erfolgreich übermittelt!", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episode} other {Episodes}}", + "components.IssueModal.CreateIssueModal.toastviewissue": "Problem ansehen", + "components.IssueModal.issueVideo": "Video", + "components.NotificationTypeSelector.adminissuecommentDescription": "Lassen Sie sich benachrichtigen, wenn neue Kommentare zu einem Problem eingehen.", + "components.NotificationTypeSelector.issuecommentDescription": "Senden Sie Benachrichtigungen, wenn Probleme neue Kommentare erhalten.", + "components.NotificationTypeSelector.issuecreated": "Gemeldetes Problem", + "components.PermissionEdit.manageissues": "Probleme verwalten", + "components.PermissionEdit.viewissues": "Probleme ansehen", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Anwendungs-API-Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registrieren Sie eine Anwendung zur Verwendung mit {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Benutzer- oder Gruppenschlüssel", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Ihre 30-stellige Benutzer- oder Gruppenkennung", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Die Einstellungen für die Pushover-Benachrichtigung konnten nicht gespeichert werden.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover-Benachrichtigungseinstellungen erfolgreich gespeichert!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Sie müssen ein Zugriffs-Token angeben", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Sie müssen ein gültiges Anwendungs-Token angeben", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Du musst einen gültigen Benutzer- oder Gruppenschlüssel angeben", + "i18n.resolved": "Gelöst", + "components.PermissionEdit.viewissuesDescription": "Berechtigt, von andereren Nutzern gemeldete Medienprobleme zu sehen.", + "components.PermissionEdit.createissuesDescription": "Berechtigt, Medienprobleme zu melden.", + "components.PermissionEdit.manageissuesDescription": "Berechtigt, Medienprobleme zu verwalten." } diff --git a/src/i18n/locale/el.json b/src/i18n/locale/el.json index 5d5805052..48cdda544 100644 --- a/src/i18n/locale/el.json +++ b/src/i18n/locale/el.json @@ -58,16 +58,9 @@ "components.MovieDetails.overview": "Επισκόπηση", "components.MovieDetails.originaltitle": "Αρχικός Τίτλος", "components.MovieDetails.originallanguage": "Αρχική Γλώσσα", - "components.MovieDetails.openradarr4k": "Άνοιγμα Ταινίας στο 4K Radarr", - "components.MovieDetails.openradarr": "Άνοιγμα Ταινίας στο Radarr", "components.MovieDetails.markavailable": "Σήμανση ως Διαθέσιμο", "components.MovieDetails.mark4kavailable": "Σήμανση ως Διαθέσιμο σε 4K", - "components.MovieDetails.manageModalTitle": "Διαχείριση Ταινίας", - "components.MovieDetails.manageModalClearMediaWarning": "* Αυτό θα αφαιρέσει αμετάκλητα όλα τα δεδομένα για αυτή την ταινία, συμπεριλαμβανομένων των αιτημάτων. Εάν αυτό το στοιχείο υπάρχει στη βιβλιοθήκη του Plex, οι πληροφορίες των μέσων θα δημιουργηθούν ξανά κατά την επόμενη σάρωση.", - "components.MovieDetails.manageModalNoRequests": "Δεν υπάρχουν αιτήματα.", - "components.MovieDetails.manageModalClearMedia": "Εκκαθάριση δεδομένων μέσων", "components.MovieDetails.MovieCast.fullcast": "Όλοι οι Ηθοποιοί", - "components.MovieDetails.downloadstatus": "Κατάσταση Λήψης", "components.MovieDetails.cast": "Ηθοποιοί", "components.MovieDetails.MovieCrew.fullcrew": "Πλήρες Συνεργείο", "components.MovieDetails.budget": "Προϋπολογισμός", @@ -102,11 +95,9 @@ "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Ταινίες", "components.CollectionDetails.requestcollection4k": "Αίτημα Συλλογής σε 4K", "components.CollectionDetails.requestcollection": "Αίτημα Συλλογής", - "components.CollectionDetails.requestSuccess": "{title} ζητήθηκε επιτυχώς!", "components.CollectionDetails.overview": "Επισκόπηση", "components.CollectionDetails.numberofmovies": "{count} Ταινίες", "pages.somethingwentwrong": "Κάτι πήγε στραβά", - "components.MovieDetails.manageModalRequests": "Αιτήματα", "components.Layout.UserDropdown.signout": "Αποσύνδεση", "components.Layout.UserDropdown.settings": "Ρυθμίσεις", "components.Layout.UserDropdown.myprofile": "Προφίλ", @@ -133,7 +124,7 @@ "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "Αποτυχία αποστολής δοκιμαστικής ειδοποίησης LunaSea.", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "Οι ρυθμίσεις ειδοποιήσεων LunaSea αποθηκεύτηκαν με επιτυχία!", "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "Οι ρυθμίσεις των ειδοποιήσεων LunaSea δεν κατάφεραν να αποθηκευτούν.", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Χρειάζεται μόνο εφόσον δεν χρησιμοποιείται το προεπιλεγμένο προφίλ", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Χρειάζεται μόνο εφόσον δεν χρησιμοποιείται το default προφίλ", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Όνομα Προφίλ", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Ενεργοποίηση του Μεταφορέα", "components.Search.searchresults": "Αποτελέσματα αναζήτησης", @@ -160,7 +151,6 @@ "components.RequestModal.requesterror": "Κάτι πήγε στραβά κατά την υποβολή του αιτήματος.", "components.RequestModal.requestedited": "Το αίτημα για {title} επεξεργάστηκε με επιτυχία!", "components.RequestModal.requestcancelled": "Το αίτημα για {title} ακυρώθηκε.", - "components.RequestModal.requestall": "Ζήτα Όλες τις Σεζόν", "components.RequestModal.requestadmin": "Αυτό το αίτημα θα εγκριθεί αυτόματα.", "components.RequestModal.requestSuccess": "{title} ζητήθηκε επιτυχώς!", "components.RequestModal.requestCancel": "Το αίτημα για {title} ακυρώθηκε.", @@ -203,8 +193,6 @@ "components.PersonDetails.ascharacter": "ως {character}", "components.PermissionEdit.usersDescription": "Εκχώρηση άδειας για διαχείρηση των Overseerr χρηστών. Οι χρήστες με αυτή την άδεια δεν μπορούν να τροποποιήσουν τους χρήστες με το προνόμιο του διαχειριστή ή να κάνουν κάποιον διαχειριστή.", "components.Layout.VersionStatus.streamdevelop": "Overseerr Develop", - "components.CollectionDetails.requestswillbecreated4k": "Οι ακόλουθοι τίτλοι θα έχουν 4K αιτήματα για:", - "components.CollectionDetails.requestswillbecreated": "Οι ακόλουθοι τίτλοι θα δημιουργήσουν αιτήματα για:", "components.AppDataWarning.dockerVolumeMissingDescription": "Η {appDataPath} προσάρτηση τόμου δεν έχει ρυθμιστεί σωστά. Όλα τα δεδομένα θα διαγραφούν όταν ο περιέχοντας σταματήσει ή επανεκκινήσει.", "components.RequestModal.AdvancedRequester.rootfolder": "Ριζικός φάκελος", "components.RequestModal.QuotaDisplay.movie": "ταινία", @@ -600,7 +588,6 @@ "components.UserList.usercreatedfailed": "Κάτι πήγε στραβά κατά τη δημιουργία του χρήστη.", "components.UserList.user": "Χρήστης", "components.UserList.totalrequests": "Σύνολο αιτημάτων", - "components.UserList.sortUpdated": "Ενημερώθηκε τελευταία", "components.UserList.sortRequests": "Αριθμός αιτημάτων", "components.UserList.sortDisplayName": "Εμφανιζόμενο όνομα", "components.UserList.sortCreated": "Ημερομηνία δημιουργίας", @@ -612,7 +599,6 @@ "components.UserList.nouserstoimport": "Δεν υπάρχουν νέοι χρήστες για εισαγωγή από το Plex.", "components.UserList.localuser": "Τοπικός χρήστης", "components.UserList.localLoginDisabled": "Η ρύθμιση Ενεργοποίηση τοπικής σύνδεσης είναι προς το παρόν απενεργοποιημένη.", - "components.UserList.lastupdated": "Ενημερώθηκε τελευταία", "components.UserList.importfromplexerror": "Κάτι πήγε στραβά κατά την εισαγωγή χρηστών από το Plex.", "components.UserList.importfromplex": "Εισαγωγή χρηστών από το Plex", "components.UserList.importedfromplex": "{userCount, plural, one {# νέου χρήστη} other {#νέοι χρήστες}} εισήχθησαν απο το Plex επιτυχώς!", @@ -641,24 +627,13 @@ "components.TvDetails.overview": "Επισκόπηση", "components.TvDetails.originaltitle": "Αρχικός τίτλος", "components.TvDetails.originallanguage": "Αρχική Γλώσσα", - "components.TvDetails.opensonarr4k": "Άνοιγμα Σειράς στο 4K Sonarr", - "components.TvDetails.opensonarr": "Άνοιγμα Σειράς στο Sonarr", "components.TvDetails.nextAirDate": "Επόμενη ημερομηνία προβολής", "components.TvDetails.network": "{networkCount, plural, one {Δίκτυο} other {Δίκτυα}}", - "components.TvDetails.markavailable": "Σήμανση ως Διαθέσιμο", - "components.TvDetails.mark4kavailable": "Σήμανση ως Διαθέσιμο σε 4K", - "components.TvDetails.manageModalTitle": "Διαχείριση σειράς", - "components.TvDetails.manageModalRequests": "Αιτήματα", - "components.TvDetails.manageModalNoRequests": "Δεν υπάρχουν αιτήματα.", - "components.TvDetails.manageModalClearMediaWarning": "* Αυτό θα αφαιρέσει αμετάκλητα όλα τα δεδομένα για αυτή την σειρά, συμπεριλαμβανομένων των αιτημάτων. Εάν αυτό το στοιχείο υπάρχει στη βιβλιοθήκη του Plex, οι πληροφορίες των μέσων θα δημιουργηθούν ξανά κατά την επόμενη σάρωση.", - "components.TvDetails.manageModalClearMedia": "Εκκαθάριση δεδομένων μέσων", "components.TvDetails.firstAirDate": "Ημερομηνία πρώτης προβολής", "components.TvDetails.episodeRuntimeMinutes": "{runtime} λεπτά", "components.TvDetails.episodeRuntime": "Διάρκεια επεισοδίου", - "components.TvDetails.downloadstatus": "Κατάσταση Λήψης", "components.TvDetails.cast": "Πρωταγωνιστές", "components.TvDetails.anime": "Anime", - "components.TvDetails.allseasonsmarkedavailable": "* Όλες οι Σειρές θα επισημανθούν ως διαθέσιμες.", "components.TvDetails.TvCrew.fullseriescrew": "Όλο το Πλήρωμα της Σειράς", "components.TvDetails.TvCast.fullseriescast": "Όλοι οι Ηθοποιοί της Σειράς", "components.StatusChacker.reloadOverseerr": "Επαναφόρτωση", diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 789d05a7e..bb63ca598 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -2,11 +2,8 @@ "components.AppDataWarning.dockerVolumeMissingDescription": "The {appDataPath} volume mount was not configured properly. All data will be cleared when the container is stopped or restarted.", "components.CollectionDetails.numberofmovies": "{count} Movies", "components.CollectionDetails.overview": "Overview", - "components.CollectionDetails.requestSuccess": "{title} requested successfully!", "components.CollectionDetails.requestcollection": "Request Collection", "components.CollectionDetails.requestcollection4k": "Request Collection in 4K", - "components.CollectionDetails.requestswillbecreated": "The following titles will have requests created for them:", - "components.CollectionDetails.requestswillbecreated4k": "The following titles will have 4K requests created for them:", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Movies", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Movies", "components.Discover.DiscoverNetwork.networkSeries": "{network} Series", @@ -32,11 +29,85 @@ "components.Discover.upcomingmovies": "Upcoming Movies", "components.Discover.upcomingtv": "Upcoming Series", "components.DownloadBlock.estimatedtime": "Estimated {time}", + "components.IssueDetails.IssueComment.areyousuredelete": "Are you sure you want to delete this comment?", + "components.IssueDetails.IssueComment.delete": "Delete Comment", + "components.IssueDetails.IssueComment.edit": "Edit Comment", + "components.IssueDetails.IssueComment.postedby": "Posted {relativeTime} by {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Posted {relativeTime} by {username} (Edited)", + "components.IssueDetails.IssueComment.validationComment": "You must enter a message", + "components.IssueDetails.IssueDescription.deleteissue": "Delete Issue", + "components.IssueDetails.IssueDescription.description": "Description", + "components.IssueDetails.IssueDescription.edit": "Edit Description", + "components.IssueDetails.allepisodes": "All Episodes", + "components.IssueDetails.allseasons": "All Seasons", + "components.IssueDetails.closeissue": "Close Issue", + "components.IssueDetails.closeissueandcomment": "Close with Comment", + "components.IssueDetails.commentplaceholder": "Add a comment…", + "components.IssueDetails.comments": "Comments", + "components.IssueDetails.deleteissue": "Delete Issue", + "components.IssueDetails.deleteissueconfirm": "Are you sure you want to delete this issue?", + "components.IssueDetails.episode": "Episode {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Issue", + "components.IssueDetails.issuetype": "Type", + "components.IssueDetails.lastupdated": "Last Updated", + "components.IssueDetails.leavecomment": "Comment", + "components.IssueDetails.nocomments": "No comments.", + "components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by {username}", + "components.IssueDetails.openin4karr": "Open in 4K {arr}", + "components.IssueDetails.openinarr": "Open in {arr}", + "components.IssueDetails.play4konplex": "Play in 4K on Plex", + "components.IssueDetails.playonplex": "Play on Plex", + "components.IssueDetails.problemepisode": "Affected Episode", + "components.IssueDetails.problemseason": "Affected Season", + "components.IssueDetails.reopenissue": "Reopen Issue", + "components.IssueDetails.reopenissueandcomment": "Reopen with Comment", + "components.IssueDetails.season": "Season {seasonNumber}", + "components.IssueDetails.toasteditdescriptionfailed": "Something went wrong while editing the issue description.", + "components.IssueDetails.toasteditdescriptionsuccess": "Issue description edited successfully!", + "components.IssueDetails.toastissuedeleted": "Issue deleted successfully!", + "components.IssueDetails.toastissuedeletefailed": "Something went wrong while deleting the issue.", + "components.IssueDetails.toaststatusupdated": "Issue status updated successfully!", + "components.IssueDetails.toaststatusupdatefailed": "Something went wrong while updating the issue status.", + "components.IssueDetails.unknownissuetype": "Unknown", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episode} other {Episodes}}", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.issuetype": "Type", + "components.IssueList.IssueItem.opened": "Opened", + "components.IssueList.IssueItem.openeduserdate": "{date} by {user}", + "components.IssueList.IssueItem.problemepisode": "Affected Episode", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}", + "components.IssueList.IssueItem.unknownissuetype": "Unknown", + "components.IssueList.IssueItem.viewissue": "View Issue", + "components.IssueList.issues": "Issues", + "components.IssueList.showallissues": "Show All Issues", + "components.IssueList.sortAdded": "Most Recent", + "components.IssueList.sortModified": "Last Modified", + "components.IssueModal.CreateIssueModal.allepisodes": "All Episodes", + "components.IssueModal.CreateIssueModal.allseasons": "All Seasons", + "components.IssueModal.CreateIssueModal.episode": "Episode {episodeNumber}", + "components.IssueModal.CreateIssueModal.extras": "Extras", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Is there a problem with {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Affected Episode", + "components.IssueModal.CreateIssueModal.problemseason": "Affected Season", + "components.IssueModal.CreateIssueModal.providedetail": "Please provide a detailed explanation of the issue you encountered.", + "components.IssueModal.CreateIssueModal.reportissue": "Report an Issue", + "components.IssueModal.CreateIssueModal.season": "Season {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Submit Issue", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Something went wrong while submitting the issue.", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Issue report for {title} submitted successfully!", + "components.IssueModal.CreateIssueModal.toastviewissue": "View Issue", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "You must provide a description", + "components.IssueModal.CreateIssueModal.whatswrong": "What's wrong?", + "components.IssueModal.issueAudio": "Audio", + "components.IssueModal.issueOther": "Other", + "components.IssueModal.issueSubtitles": "Subtitle", + "components.IssueModal.issueVideo": "Video", "components.LanguageSelector.languageServerDefault": "Default ({language})", "components.LanguageSelector.originalLanguageDefault": "All Languages", "components.Layout.LanguagePicker.displaylanguage": "Display Language", "components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV", "components.Layout.Sidebar.dashboard": "Discover", + "components.Layout.Sidebar.issues": "Issues", "components.Layout.Sidebar.requests": "Requests", "components.Layout.Sidebar.settings": "Settings", "components.Layout.Sidebar.users": "Users", @@ -58,27 +129,34 @@ "components.Login.signinwithplex": "Use your Plex account", "components.Login.validationemailrequired": "You must provide a valid email address", "components.Login.validationpasswordrequired": "You must provide a password", + "components.ManageSlideOver.allseasonsmarkedavailable": "* All seasons will be marked as available.", + "components.ManageSlideOver.downloadstatus": "Download Status", + "components.ManageSlideOver.manageModalClearMedia": "Clear Media Data", + "components.ManageSlideOver.manageModalClearMediaWarning": "* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", + "components.ManageSlideOver.manageModalIssues": "Open Issues", + "components.ManageSlideOver.manageModalNoRequests": "No requests.", + "components.ManageSlideOver.manageModalRequests": "Requests", + "components.ManageSlideOver.manageModalTitle": "Manage {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Mark as Available in 4K", + "components.ManageSlideOver.markavailable": "Mark as Available", + "components.ManageSlideOver.movie": "movie", + "components.ManageSlideOver.openarr": "Open in {arr}", + "components.ManageSlideOver.openarr4k": "Open in 4K {arr}", + "components.ManageSlideOver.tvshow": "series", "components.MediaSlider.ShowMoreCard.seemore": "See More", "components.MovieDetails.MovieCast.fullcast": "Full Cast", "components.MovieDetails.MovieCrew.fullcrew": "Full Crew", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cast": "Cast", - "components.MovieDetails.downloadstatus": "Download Status", - "components.MovieDetails.manageModalClearMedia": "Clear Media Data", - "components.MovieDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this movie, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", - "components.MovieDetails.manageModalNoRequests": "No requests.", - "components.MovieDetails.manageModalRequests": "Requests", - "components.MovieDetails.manageModalTitle": "Manage Movie", "components.MovieDetails.mark4kavailable": "Mark as Available in 4K", "components.MovieDetails.markavailable": "Mark as Available", - "components.MovieDetails.openradarr": "Open Movie in Radarr", - "components.MovieDetails.openradarr4k": "Open Movie in 4K Radarr", "components.MovieDetails.originallanguage": "Original Language", "components.MovieDetails.originaltitle": "Original Title", "components.MovieDetails.overview": "Overview", "components.MovieDetails.overviewunavailable": "Overview unavailable.", "components.MovieDetails.play4konplex": "Play in 4K on Plex", "components.MovieDetails.playonplex": "Play on Plex", + "components.MovieDetails.productioncountries": "Production {countryCount, plural, one {Country} other {Countries}}", "components.MovieDetails.recommendations": "Recommendations", "components.MovieDetails.releasedate": "{releaseCount, plural, one {Release Date} other {Release Dates}}", "components.MovieDetails.revenue": "Revenue", @@ -90,6 +168,17 @@ "components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studios}}", "components.MovieDetails.viewfullcrew": "View Full Crew", "components.MovieDetails.watchtrailer": "Watch Trailer", + "components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Get notified when issues are reopened by other users.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Get notified when issues are resolved by other users.", + "components.NotificationTypeSelector.issuecomment": "Issue Comment", + "components.NotificationTypeSelector.issuecommentDescription": "Send notifications when issues receive new comments.", + "components.NotificationTypeSelector.issuecreated": "Issue Reported", + "components.NotificationTypeSelector.issuecreatedDescription": "Send notifications when issues are reported.", + "components.NotificationTypeSelector.issuereopened": "Issue Reopened", + "components.NotificationTypeSelector.issuereopenedDescription": "Send notifications when issues are reopened.", + "components.NotificationTypeSelector.issueresolved": "Issue Resolved", + "components.NotificationTypeSelector.issueresolvedDescription": "Send notifications when issues are resolved.", "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatically Approved", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Send notifications when users submit new media requests which are automatically approved.", "components.NotificationTypeSelector.mediaapproved": "Media Approved", @@ -103,6 +192,10 @@ "components.NotificationTypeSelector.mediarequested": "Media Requested", "components.NotificationTypeSelector.mediarequestedDescription": "Send notifications when users submit new media requests which require approval.", "components.NotificationTypeSelector.notificationTypes": "Notification Types", + "components.NotificationTypeSelector.userissuecommentDescription": "Get notified when issues you reported receive new comments.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Get notified when other users report issues.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Get notified when issues you reported are reopened.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Get notified when issues you reported are resolved.", "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Get notified when other users submit new media requests which are automatically approved.", "components.NotificationTypeSelector.usermediaapprovedDescription": "Get notified when your media requests are approved.", "components.NotificationTypeSelector.usermediaavailableDescription": "Get notified when your media requests become available.", @@ -112,39 +205,45 @@ "components.PermissionEdit.admin": "Admin", "components.PermissionEdit.adminDescription": "Full administrator access. Bypasses all other permission checks.", "components.PermissionEdit.advancedrequest": "Advanced Requests", - "components.PermissionEdit.advancedrequestDescription": "Grant permission to use advanced request options.", + "components.PermissionEdit.advancedrequestDescription": "Grant permission to modify advanced media request options.", "components.PermissionEdit.autoapprove": "Auto-Approve", "components.PermissionEdit.autoapprove4k": "Auto-Approve 4K", - "components.PermissionEdit.autoapprove4kDescription": "Grant automatic approval for all 4K requests.", + "components.PermissionEdit.autoapprove4kDescription": "Grant automatic approval for all 4K media requests.", "components.PermissionEdit.autoapprove4kMovies": "Auto-Approve 4K Movies", "components.PermissionEdit.autoapprove4kMoviesDescription": "Grant automatic approval for 4K movie requests.", "components.PermissionEdit.autoapprove4kSeries": "Auto-Approve 4K Series", "components.PermissionEdit.autoapprove4kSeriesDescription": "Grant automatic approval for 4K series requests.", - "components.PermissionEdit.autoapproveDescription": "Grant automatic approval for all non-4K requests.", + "components.PermissionEdit.autoapproveDescription": "Grant automatic approval for all non-4K media requests.", "components.PermissionEdit.autoapproveMovies": "Auto-Approve Movies", "components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.", "components.PermissionEdit.autoapproveSeries": "Auto-Approve Series", "components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.", + "components.PermissionEdit.createissues": "Report Issues", + "components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.", + "components.PermissionEdit.manageissues": "Manage Issues", + "components.PermissionEdit.manageissuesDescription": "Grant permission to manage media issues.", "components.PermissionEdit.managerequests": "Manage Requests", - "components.PermissionEdit.managerequestsDescription": "Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.", + "components.PermissionEdit.managerequestsDescription": "Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.", "components.PermissionEdit.request": "Request", "components.PermissionEdit.request4k": "Request 4K", - "components.PermissionEdit.request4kDescription": "Grant permission to request 4K media.", + "components.PermissionEdit.request4kDescription": "Grant permission to submit requests for 4K media.", "components.PermissionEdit.request4kMovies": "Request 4K Movies", - "components.PermissionEdit.request4kMoviesDescription": "Grant permission to request 4K movies.", + "components.PermissionEdit.request4kMoviesDescription": "Grant permission to submit requests for 4K movies.", "components.PermissionEdit.request4kTv": "Request 4K Series", - "components.PermissionEdit.request4kTvDescription": "Grant permission to request 4K series.", - "components.PermissionEdit.requestDescription": "Grant permission to request non-4K media.", + "components.PermissionEdit.request4kTvDescription": "Grant permission to submit requests for 4K series.", + "components.PermissionEdit.requestDescription": "Grant permission to submit requests for non-4K media.", "components.PermissionEdit.requestMovies": "Request Movies", - "components.PermissionEdit.requestMoviesDescription": "Grant permission to request non-4K movies.", + "components.PermissionEdit.requestMoviesDescription": "Grant permission to submit requests for non-4K movies.", "components.PermissionEdit.requestTv": "Request Series", - "components.PermissionEdit.requestTvDescription": "Grant permission to request non-4K series.", + "components.PermissionEdit.requestTvDescription": "Grant permission to submit requests for non-4K series.", "components.PermissionEdit.settings": "Manage Settings", - "components.PermissionEdit.settingsDescription": "Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.", + "components.PermissionEdit.settingsDescription": "Grant permission to modify global settings. A user must have this permission to grant it to others.", "components.PermissionEdit.users": "Manage Users", - "components.PermissionEdit.usersDescription": "Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.", + "components.PermissionEdit.usersDescription": "Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.", + "components.PermissionEdit.viewissues": "View Issues", + "components.PermissionEdit.viewissuesDescription": "Grant permission to view media issues reported by other users.", "components.PermissionEdit.viewrequests": "View Requests", - "components.PermissionEdit.viewrequestsDescription": "Grant permission to view other users' requests.", + "components.PermissionEdit.viewrequestsDescription": "Grant permission to view media requests submitted by other users.", "components.PersonDetails.alsoknownas": "Also Known As: {names}", "components.PersonDetails.appearsin": "Appearances", "components.PersonDetails.ascharacter": "as {character}", @@ -194,7 +293,7 @@ "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}", "components.RequestList.requests": "Requests", "components.RequestList.showallrequests": "Show All Requests", - "components.RequestList.sortAdded": "Request Date", + "components.RequestList.sortAdded": "Most Recent", "components.RequestList.sortModified": "Last Modified", "components.RequestModal.AdvancedRequester.advancedoptions": "Advanced", "components.RequestModal.AdvancedRequester.animenote": "* This series is an anime.", @@ -223,6 +322,7 @@ "components.RequestModal.SearchByNameModal.nosummary": "No summary for this title was found.", "components.RequestModal.SearchByNameModal.notvdbiddescription": "We couldn't automatically match your request. Please select the correct match from the list below.", "components.RequestModal.alreadyrequested": "Already Requested", + "components.RequestModal.approve": "Approve Request", "components.RequestModal.autoapproval": "Automatic Approval", "components.RequestModal.cancel": "Cancel Request", "components.RequestModal.edit": "Edit Request", @@ -233,18 +333,22 @@ "components.RequestModal.pendingapproval": "Your request is pending approval.", "components.RequestModal.pendingrequest": "Pending Request for {title}", "components.RequestModal.request4ktitle": "Request {title} in 4K", + "components.RequestModal.requestApproved": "Request for {title} approved!", "components.RequestModal.requestCancel": "Request for {title} canceled.", "components.RequestModal.requestSuccess": "{title} requested successfully!", "components.RequestModal.requestadmin": "This request will be approved automatically.", - "components.RequestModal.requestall": "Request All Seasons", "components.RequestModal.requestcancelled": "Request for {title} canceled.", "components.RequestModal.requestedited": "Request for {title} edited successfully!", "components.RequestModal.requesterror": "Something went wrong while submitting the request.", "components.RequestModal.requestfrom": "{username}'s request is pending approval.", + "components.RequestModal.requestmovies": "Request {count} {count, plural, one {Movie} other {Movies}}", + "components.RequestModal.requestmovies4k": "Request {count} {count, plural, one {Movie} other {Movies}} in 4K", "components.RequestModal.requestseasons": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}", + "components.RequestModal.requestseasons4k": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}} in 4K", "components.RequestModal.requesttitle": "Request {title}", "components.RequestModal.season": "Season", "components.RequestModal.seasonnumber": "Season {number}", + "components.RequestModal.selectmovies": "Select Movie(s)", "components.RequestModal.selectseason": "Select Season(s)", "components.ResetPassword.confirmpassword": "Confirm Password", "components.ResetPassword.email": "Email Address", @@ -420,7 +524,7 @@ "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Base URL must have a leading slash", "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Base URL must not end in a trailing slash", - "components.Settings.RadarrModal.validationHostnameRequired": "You must provide a hostname or IP address", + "components.Settings.RadarrModal.validationHostnameRequired": "You must provide a valid hostname or IP address", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "You must select a minimum availability", "components.Settings.RadarrModal.validationNameRequired": "You must provide a server name", "components.Settings.RadarrModal.validationPortRequired": "You must provide a valid port number", @@ -560,7 +664,7 @@ "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Base URL must have a leading slash", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Base URL must not end in a trailing slash", - "components.Settings.SonarrModal.validationHostnameRequired": "You must provide a hostname or IP address", + "components.Settings.SonarrModal.validationHostnameRequired": "You must provide a valid hostname or IP address", "components.Settings.SonarrModal.validationLanguageProfileRequired": "You must select a language profile", "components.Settings.SonarrModal.validationNameRequired": "You must provide a server name", "components.Settings.SonarrModal.validationPortRequired": "You must provide a valid port number", @@ -680,30 +784,20 @@ "components.StatusChacker.reloadOverseerr": "Reload", "components.TvDetails.TvCast.fullseriescast": "Full Series Cast", "components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew", - "components.TvDetails.allseasonsmarkedavailable": "* All seasons will be marked as available.", "components.TvDetails.anime": "Anime", "components.TvDetails.cast": "Cast", - "components.TvDetails.downloadstatus": "Download Status", "components.TvDetails.episodeRuntime": "Episode Runtime", "components.TvDetails.episodeRuntimeMinutes": "{runtime} minutes", "components.TvDetails.firstAirDate": "First Air Date", - "components.TvDetails.manageModalClearMedia": "Clear Media Data", - "components.TvDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", - "components.TvDetails.manageModalNoRequests": "No requests.", - "components.TvDetails.manageModalRequests": "Requests", - "components.TvDetails.manageModalTitle": "Manage Series", - "components.TvDetails.mark4kavailable": "Mark as Available in 4K", - "components.TvDetails.markavailable": "Mark as Available", "components.TvDetails.network": "{networkCount, plural, one {Network} other {Networks}}", "components.TvDetails.nextAirDate": "Next Air Date", - "components.TvDetails.opensonarr": "Open Series in Sonarr", - "components.TvDetails.opensonarr4k": "Open Series in 4K Sonarr", "components.TvDetails.originallanguage": "Original Language", "components.TvDetails.originaltitle": "Original Title", "components.TvDetails.overview": "Overview", "components.TvDetails.overviewunavailable": "Overview unavailable.", "components.TvDetails.play4konplex": "Play in 4K on Plex", "components.TvDetails.playonplex": "Play on Plex", + "components.TvDetails.productioncountries": "Production {countryCount, plural, one {Country} other {Countries}}", "components.TvDetails.recommendations": "Recommendations", "components.TvDetails.seasons": "{seasonCount, plural, one {# Season} other {# Seasons}}", "components.TvDetails.showtype": "Series Type", @@ -717,7 +811,7 @@ "components.UserList.autogeneratepasswordTip": "Email a server-generated password to the user", "components.UserList.bulkedit": "Bulk Edit", "components.UserList.create": "Create", - "components.UserList.created": "Created", + "components.UserList.created": "Joined", "components.UserList.createlocaluser": "Create Local User", "components.UserList.creating": "Creating…", "components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.", @@ -728,7 +822,6 @@ "components.UserList.importedfromplex": "{userCount, plural, one {# new user} other {# new users}} imported from Plex successfully!", "components.UserList.importfromplex": "Import Users from Plex", "components.UserList.importfromplexerror": "Something went wrong while importing users from Plex.", - "components.UserList.lastupdated": "Updated", "components.UserList.localLoginDisabled": "The Enable Local Sign-In setting is currently disabled.", "components.UserList.localuser": "Local User", "components.UserList.nouserstoimport": "No new users to import from Plex.", @@ -737,10 +830,9 @@ "components.UserList.passwordinfodescription": "Configure an application URL and enable email notifications to allow automatic password generation.", "components.UserList.plexuser": "Plex User", "components.UserList.role": "Role", - "components.UserList.sortCreated": "Creation Date", + "components.UserList.sortCreated": "Join Date", "components.UserList.sortDisplayName": "Display Name", "components.UserList.sortRequests": "Request Count", - "components.UserList.sortUpdated": "Last Updated", "components.UserList.totalrequests": "Requests", "components.UserList.user": "User", "components.UserList.usercreatedfailed": "Something went wrong while creating the user.", @@ -790,6 +882,16 @@ "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Notification Settings", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "PGP Public Key", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Encrypt email messages using OpenPGP", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Access Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Create a token from your Account Settings", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet notification settings failed to save.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet notification settings saved successfully!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Application API Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Register an application for use with {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "User or Group Key", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Your 30-character user or group identifier", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover notification settings failed to save.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Send Silently", "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Send notifications with no sound", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Chat ID", @@ -798,6 +900,9 @@ "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid user ID", "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "You must provide a valid PGP public key", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "You must provide an access token", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "You must provide a valid application token", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "You must provide a valid user or group key", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID", "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Web push notification settings failed to save.", @@ -859,6 +964,7 @@ "i18n.next": "Next", "i18n.noresults": "No results.", "i18n.notrequested": "Not Requested", + "i18n.open": "Open", "i18n.partiallyavailable": "Partially Available", "i18n.pending": "Pending", "i18n.previous": "Previous", @@ -867,6 +973,7 @@ "i18n.request4k": "Request in 4K", "i18n.requested": "Requested", "i18n.requesting": "Requesting…", + "i18n.resolved": "Resolved", "i18n.resultsperpage": "Display {pageSize} results per page", "i18n.retry": "Retry", "i18n.retrying": "Retrying…", diff --git a/src/i18n/locale/es.json b/src/i18n/locale/es.json index a37e0b288..75d460fb8 100644 --- a/src/i18n/locale/es.json +++ b/src/i18n/locale/es.json @@ -18,14 +18,14 @@ "components.Settings.SettingsAbout.version": "Versión", "components.Settings.SettingsAbout.totalrequests": "Peticiones Totales", "components.Settings.SettingsAbout.totalmedia": "Contenido Total", - "components.Settings.SettingsAbout.overseerrinformation": "Información de Overseerr", + "components.Settings.SettingsAbout.overseerrinformation": "Sobre Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "Discursiones en GitHub", "components.Settings.SettingsAbout.gettingsupport": "Soporte", "components.Settings.RadarrModal.validationRootFolderRequired": "Debes seleccionar una carpeta raíz", "components.Settings.RadarrModal.validationProfileRequired": "Debes seleccionar un perfil de calidad", "components.Settings.RadarrModal.validationPortRequired": "Debes proporcionar un número de puerto válido", "components.Settings.RadarrModal.validationNameRequired": "Debes proporcionar un nombre de servidor", - "components.Settings.RadarrModal.validationHostnameRequired": "Debes proporcionar un nombre de host o dirección IP", + "components.Settings.RadarrModal.validationHostnameRequired": "Debes proporcionar un nombre de host o dirección IP válido", "components.Settings.RadarrModal.validationApiKeyRequired": "Debes proporcionar la clave API", "components.Settings.RadarrModal.toastRadarrTestSuccess": "¡Conexión con Radarr establecida con éxito!", "components.Settings.RadarrModal.toastRadarrTestFailure": "Error al connectar al Radarr.", @@ -65,15 +65,15 @@ "components.RequestModal.season": "Temporada", "components.RequestModal.requesttitle": "Solicitar {title}", "components.RequestModal.requestseasons": "Solicitar {seasonCount} {seasonCount, plural, one {Temporada} other {Temporadas}}", - "components.RequestModal.requestfrom": "La petición de {username} está pendiente de aprobación.", - "components.RequestModal.requestadmin": "Esta petición será aprobada automáticamente.", + "components.RequestModal.requestfrom": "La solicitud de {username} está pendiente de aprobación.", + "components.RequestModal.requestadmin": "Esta solicitud será aprobada automáticamente.", "components.RequestModal.requestSuccess": "¡{title} solicitada con éxito!", "components.RequestModal.requestCancel": "Solicitud para {title} cancelada.", "components.RequestModal.pendingrequest": "Solicitud pendiente para {title}", "components.RequestModal.numberofepisodes": "# de Episodios", "components.RequestModal.extras": "Extras", "components.RequestModal.cancel": "Cancelar Petición", - "components.RequestList.requests": "Peticiones", + "components.RequestList.requests": "Solicitudes", "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}", "components.RequestCard.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}", "components.RequestBlock.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}", @@ -89,16 +89,11 @@ "components.MovieDetails.overviewunavailable": "Resumen indisponible.", "components.MovieDetails.overview": "Resumen", "components.MovieDetails.originallanguage": "Idioma Original", - "components.MovieDetails.manageModalTitle": "Gestionar Película", - "components.MovieDetails.manageModalRequests": "Peticiones", - "components.MovieDetails.manageModalNoRequests": "Sin peticiones.", - "components.MovieDetails.manageModalClearMediaWarning": "* Esto borrará todos los datos de esta película, incluyendo las peticiones. Si el elemento existe en tu librería de Plex, la información del elemento se recreará en el siguiente escaneo.", - "components.MovieDetails.manageModalClearMedia": "Borrar los datos de medios", "components.MovieDetails.budget": "Presupuesto", "components.Layout.UserDropdown.signout": "Cerrar Sesión", "components.Layout.Sidebar.users": "Usuarios", "components.Layout.Sidebar.settings": "Ajustes", - "components.Layout.Sidebar.requests": "Peticiones", + "components.Layout.Sidebar.requests": "Solicitudes", "components.Layout.Sidebar.dashboard": "Descubrir", "components.Layout.SearchInput.searchPlaceholder": "Buscar Películas y Series", "components.Discover.upcomingmovies": "Próximas Películas", @@ -118,7 +113,7 @@ "components.Settings.SonarrModal.validationProfileRequired": "Debes seleccionar un perfil", "components.Settings.SonarrModal.validationPortRequired": "Debes proporcionar un número de puerto valido", "components.Settings.SonarrModal.validationNameRequired": "Debes proporcionar un nombre de servidor", - "components.Settings.SonarrModal.validationHostnameRequired": "Debes proporcionar un nombre de host o dirección IP", + "components.Settings.SonarrModal.validationHostnameRequired": "Debes proporcionar un nombre de host o dirección IP válido", "components.Settings.SonarrModal.validationApiKeyRequired": "Debes proporcionar la clave API", "components.Settings.menuLogs": "Registro", "pages.returnHome": "Volver al Inicio", @@ -141,19 +136,13 @@ "components.UserList.totalrequests": "Solicitudes", "components.UserList.role": "Rol", "components.UserList.plexuser": "Usuario de Plex", - "components.UserList.lastupdated": "Actualizado", - "components.UserList.created": "Creado", + "components.UserList.created": "Unido", "components.UserList.admin": "Administrador", "components.TvDetails.similar": "Series Similares", "components.TvDetails.recommendations": "Recomendaciones", "components.TvDetails.overviewunavailable": "Resumen indisponible.", "components.TvDetails.overview": "Resumen", "components.TvDetails.originallanguage": "Idioma original", - "components.TvDetails.manageModalTitle": "Gestionar Series", - "components.TvDetails.manageModalRequests": "Peticiones", - "components.TvDetails.manageModalNoRequests": "Sin peticiones.", - "components.TvDetails.manageModalClearMediaWarning": "* Esto borrará de forma irreversible todos los datos de las series, incluyendo las peticiones. Si el elemento existe en tu librería de Plex, la información del elemento se recreará en el siguiente escaneo.", - "components.TvDetails.manageModalClearMedia": "Borrar los datos de medios", "components.TvDetails.cast": "Reparto", "components.TvDetails.TvCast.fullseriescast": "Reparto completo de la serie", "components.Setup.welcome": "Bienvenido a Overseerr", @@ -227,11 +216,11 @@ "components.Settings.SettingsAbout.helppaycoffee": "Ayúdame invitándome a un café", "components.Settings.SettingsAbout.Releases.viewongithub": "Ver en GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Ver registro de cambios", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Cambios de la versión", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Cambios de la versión {version}", "components.Settings.SettingsAbout.Releases.releases": "Versiones", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Información de la versión no disponible. ¿GitHub está caído?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "La fecha de lanzamiento no está actualmente disponible.", "components.Settings.SettingsAbout.Releases.latestversion": "Última Versión", - "components.Settings.SettingsAbout.Releases.currentversion": "Versión Actual", + "components.Settings.SettingsAbout.Releases.currentversion": "Actual", "components.MovieDetails.studio": "{studioCount, plural, one {Estudio} other {Estudios}}", "components.UserList.importfromplexerror": "Algo salió mal importando usuarios de Plex.", "components.UserList.importfromplex": "Importar Usuarios de Plex", @@ -243,9 +232,7 @@ "components.PersonDetails.crewmember": "Equipo", "components.MovieDetails.viewfullcrew": "Ver Equipo Completo", "components.MovieDetails.MovieCrew.fullcrew": "Equipo Completo", - "components.CollectionDetails.requestswillbecreated": "Los siguientes títulos tendrán solicitudes creadas para ellos:", "components.CollectionDetails.requestcollection": "Solicitar Colección", - "components.CollectionDetails.requestSuccess": "¡ {title} solicitada correctamente!", "components.CollectionDetails.overview": "Resumen", "components.CollectionDetails.numberofmovies": "{count} Películas", "i18n.retry": "Reintentar", @@ -258,7 +245,7 @@ "components.Settings.Notifications.NotificationsSlack.agentenabled": "Habilitar Agente", "components.RequestList.RequestItem.failedretry": "Algo salió mal al reintentar la solicitud.", "components.MovieDetails.watchtrailer": "Ver Trailer", - "components.NotificationTypeSelector.mediarequestedDescription": "Envía notificaciones cuando los usuarios soliciten contenidos que requieran ser aprobados.", + "components.NotificationTypeSelector.mediarequestedDescription": "Envía una notificación cuando se solicita nuevo contenido que requiere ser aprobado.", "components.StatusChacker.reloadOverseerr": "Recargar", "components.StatusChacker.newversionavailable": "Actualización de Aplicación", "components.StatusChacker.newversionDescription": "¡Overseerr se ha actualizado!Haga clic en el botón de abajo para volver a cargar la aplicación.", @@ -271,11 +258,11 @@ "components.Settings.Notifications.chatId": "ID de chat", "components.Settings.Notifications.botAPI": "Token de Autorización del Bot", "components.NotificationTypeSelector.mediarequested": "Contenido Solicitado", - "components.NotificationTypeSelector.mediafailedDescription": "Envía notificaciones cuando los contenidos solicitados fallen al agregarse a Radarr o Sonarr.", + "components.NotificationTypeSelector.mediafailedDescription": "Envía una notificación cuando el contenido no se agrega a los servicios (Radarr / Sonarr).", "components.NotificationTypeSelector.mediafailed": "Contenido Fallido", - "components.NotificationTypeSelector.mediaavailableDescription": "Envía notificaciones cuando las peticiones realizadas están disponibles.", + "components.NotificationTypeSelector.mediaavailableDescription": "Envía una notificación cuando el contenido solicitado está disponible.", "components.NotificationTypeSelector.mediaavailable": "Contenido Disponible", - "components.NotificationTypeSelector.mediaapprovedDescription": "Envía notificaciones cuando los medios solicitados son aprobados manualmente.", + "components.NotificationTypeSelector.mediaapprovedDescription": "Envía una notificación cuando el contenido solicitado es aprobado manualmente.", "components.NotificationTypeSelector.mediaapproved": "Contenido Aprobado", "i18n.request": "Solicitar", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Debes proporcionar una clave de usuario o grupo válida", @@ -286,7 +273,7 @@ "components.Settings.Notifications.NotificationsPushover.agentenabled": "Agente habilitado", "components.Settings.Notifications.NotificationsPushover.accessToken": "Token de aplicación API", "components.RequestList.sortModified": "Última modificación", - "components.RequestList.sortAdded": "Fecha de solicitud", + "components.RequestList.sortAdded": "Más Reciente", "components.RequestList.showallrequests": "Mostrar todas las solicitudes", "components.RequestBlock.requestoverrides": "Anulaciones de solicitudes", "i18n.edit": "Editar", @@ -313,7 +300,7 @@ "components.Settings.Notifications.NotificationsWebhook.authheader": "Encabezado de autorización", "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Habilitar Agente", "components.RequestModal.requestedited": "¡Solicitud para {title} modificada con éxito!", - "components.RequestModal.requestcancelled": "Petición para {title} cancelada.", + "components.RequestModal.requestcancelled": "Solicitud para {title} cancelada.", "components.RequestModal.request4ktitle": "Solicitar {title} en 4K", "components.RequestModal.pending4krequest": "Solicitud pendiente en 4K para {title}", "components.RequestModal.errorediting": "Algo salió mal al editar la solicitud.", @@ -323,19 +310,19 @@ "components.RequestModal.AdvancedRequester.default": "{name} (Predeterminado)", "components.RequestModal.AdvancedRequester.animenote": "* Esta serie es un anime.", "components.RequestModal.AdvancedRequester.advancedoptions": "Avanzadas", - "components.RequestButton.viewrequest4k": "Ver Petición 4K", - "components.RequestButton.viewrequest": "Ver Petición", + "components.RequestButton.viewrequest4k": "Ver Solicitud 4K", + "components.RequestButton.viewrequest": "Ver Solicitud", "components.RequestButton.requestmore4k": "Solicitar más en 4K", "components.RequestButton.requestmore": "Solicitar más", - "components.RequestButton.declinerequests": "Rechazar {requestCount, plural, one {petición} other {{requestCount} peticiones}}", + "components.RequestButton.declinerequests": "Rechazar {requestCount, plural, one {solicitud} other {{requestCount} solicitudes}}", "components.RequestButton.declinerequest4k": "Rechazar Solicitud 4K", "components.RequestButton.declinerequest": "Rechazar Solicitud", - "components.RequestButton.decline4krequests": "Rechazar {requestCount, plural, one {petición en 4K} other {{requestCount} peticiones en 4K}}", - "components.RequestButton.approverequests": "Aprobar {requestCount, plural, one {petición} other {{requestCount} peticiones}}", - "components.RequestButton.approverequest4k": "Aprobar solicitud 4K", - "components.RequestButton.approverequest": "Aprobar solicitud", + "components.RequestButton.decline4krequests": "Rechazar {requestCount, plural, one {solicitud en 4K} other {{requestCount} solicitudes en 4K}}", + "components.RequestButton.approverequests": "Aprobar {requestCount, plural, one {solicitud} other {{requestCount} solicitudes}}", + "components.RequestButton.approverequest4k": "Aprobar Solicitud 4K", + "components.RequestButton.approverequest": "Aprobar Solicitud", "components.RequestButton.approve4krequests": "Aprobar {requestCount, plural, one {petición en 4K} other {requestCount} peticiones en 4K}}", - "components.RequestBlock.server": "Servidor de destino", + "components.RequestBlock.server": "Servidor de Destino", "components.RequestBlock.rootfolder": "Carpeta Raíz", "components.RequestBlock.profilechanged": "Perfil de Calidad", "components.MediaSlider.ShowMoreCard.seemore": "Ver más", @@ -347,7 +334,7 @@ "components.Login.email": "Dirección de correo electrónico", "components.NotificationTypeSelector.mediadeclined": "Contenido Rechazado", "components.RequestModal.autoapproval": "Aprobación Automática", - "components.NotificationTypeSelector.mediadeclinedDescription": "Envía notificaciones cuando las peticiones sean rechazadas.", + "components.NotificationTypeSelector.mediadeclinedDescription": "Envía notificaciones cuando las solicitudes sean rechazadas.", "i18n.experimental": "Experimental", "components.Settings.hideAvailable": "Ocultar los Medios Disponibles", "components.Login.signingin": "Iniciando sesión…", @@ -372,8 +359,8 @@ "components.ResetPassword.emailresetlink": "Enviar enlace de recuperación por email", "components.ResetPassword.email": "Dirección de Email", "components.ResetPassword.confirmpassword": "Confirmar Contraseña", - "components.RequestModal.requesterror": "Algo fue mal al realizar la petición.", - "components.RequestModal.SearchByNameModal.notvdbiddescription": "No hemos podido emparejar automáticamente tu petición. Por favor, selecciona el correcto de la lista.", + "components.RequestModal.requesterror": "Algo fue mal al realizar la solicitud.", + "components.RequestModal.SearchByNameModal.notvdbiddescription": "No hemos podido emparejar automáticamente tu solicitud. Por favor, selecciona el correcto de la lista.", "components.RequestModal.SearchByNameModal.nosummary": "No se ha encontrado un resumen para este título.", "components.RequestModal.AdvancedRequester.requestas": "Pedir como", "components.RequestModal.AdvancedRequester.languageprofile": "Perfil de Idioma", @@ -383,51 +370,47 @@ "components.RequestList.RequestItem.modified": "Modificado", "components.RegionSelector.regionServerDefault": "({Region}) por defecto", "components.RegionSelector.regionDefault": "Todas las Regiones", - "components.PermissionEdit.viewrequestsDescription": "Conceder permiso para ver las peticiones de otros usuarios.", - "components.PermissionEdit.viewrequests": "Ver Peticiones", - "components.PermissionEdit.usersDescription": "Concede permiso para gestionar usuarios en Overseer. Los usuarios con este permiso no pueden modificar usuarios o conceder privilegios de Administrador.", - "components.PermissionEdit.users": "Gestionar Usuarios", - "components.PermissionEdit.settingsDescription": "Concede permiso para modificar los ajustes de Overserr. Un usuario debe tener este permiso para poder concedérselo a otros.", + "components.PermissionEdit.viewrequestsDescription": "Conceder permiso para ver las solicitudes de otros usuarios.", + "components.PermissionEdit.viewrequests": "Ver Solicitudes", + "components.PermissionEdit.usersDescription": "Concede permisos para gestionar usuarios. Los usuarios con este permiso no pueden modificar usuarios o conceder privilegios de Administrador.", + "components.PermissionEdit.users": "Gestión de Usuarios", + "components.PermissionEdit.settingsDescription": "Concede permisos para modificar los ajustes globales. Un usuario debe tener este permiso para poder concedérselo a otros.", "components.PermissionEdit.settings": "Gestionar Ajustes", - "components.PermissionEdit.requestDescription": "Concede permisos para solicitar contenidos no 4K.", - "components.PermissionEdit.request4kTvDescription": "Concede permiso para solicitar series en 4K.", + "components.PermissionEdit.requestDescription": "Concede permisos para solicitar contenidos que no sean 4K.", + "components.PermissionEdit.request4kTvDescription": "Concede permisos para solicitar series en 4K.", "components.PermissionEdit.request4kTv": "Pedir Series 4K", - "components.PermissionEdit.request4kMoviesDescription": "Concede permiso para solicitar películas en 4K.", + "components.PermissionEdit.request4kMoviesDescription": "Concede permisos para solicitar películas en 4K.", "components.PermissionEdit.request4kDescription": "Concede permisos para solicitar contenidos en 4K.", - "components.PermissionEdit.managerequestsDescription": "Concede permisos para gestionar las peticiones en Overseer. Todas las peticiones de un usuario con este permiso será aprobada automáticamente.", + "components.PermissionEdit.managerequestsDescription": "Concede permisos para gestionar las solicitudes de contenidos. Todas las solicitudes de un usuario con este permiso serán aprobadas automáticamente.", "components.PermissionEdit.request4kMovies": "Solicita Películas 4K", - "components.PermissionEdit.managerequests": "Gestionar Peticiones", - "components.PermissionEdit.autoapproveSeriesDescription": "Concede aprobación automática para todas las peticiones de series no 4K.", + "components.PermissionEdit.managerequests": "Gestionar Solicitudes", + "components.PermissionEdit.autoapproveSeriesDescription": "Concede aprobación automática para todas las solicitudes de series no 4K.", "components.PermissionEdit.autoapproveSeries": "Auto-Aprueba Series", - "components.PermissionEdit.autoapproveMoviesDescription": "Concede aprobación automática para todas las peticiones de películas no 4K.", + "components.PermissionEdit.autoapproveMoviesDescription": "Concede aprobación automática para todas las solicitudes de películas no 4K.", "components.PermissionEdit.autoapproveMovies": "Auto-Aprueba Películas", - "components.PermissionEdit.autoapproveDescription": "Concede aprobación automática para todas las peticiones no 4K.", - "components.PermissionEdit.autoapprove4kSeriesDescription": "Concede aprobación automática para todas las peticiones de series 4K.", + "components.PermissionEdit.autoapproveDescription": "Concede aprobación automática para todas las solicitudes que no sean 4K.", + "components.PermissionEdit.autoapprove4kSeriesDescription": "Concede aprobación automática para todas las solicitudes de series 4K.", "components.PermissionEdit.autoapprove4kSeries": "Auto-Aprueba Series 4K", - "components.PermissionEdit.autoapprove4kMoviesDescription": "Concede aprobación automática para todas las peticiones de películas 4K.", + "components.PermissionEdit.autoapprove4kMoviesDescription": "Concede aprobación automática para todas las solicitudes de películas 4K.", "components.PermissionEdit.autoapprove4kMovies": "Auto-Aprueba Películas 4K", - "components.PermissionEdit.autoapprove4kDescription": "Concede aprobación automática para todas las peticiones 4K.", + "components.PermissionEdit.autoapprove4kDescription": "Concede aprobación automática para todas las solicitudes de contenidos 4K.", "components.PermissionEdit.autoapprove4k": "Auto-Aprobación 4K", "components.PermissionEdit.autoapprove": "Auto-Aprobación", - "components.PermissionEdit.advancedrequestDescription": "Concede permisos para configurar opciones avanzadas en las peticiones.", - "components.PermissionEdit.advancedrequest": "Peticiones Avanzadas", + "components.PermissionEdit.advancedrequestDescription": "Concede permisos para configurar opciones avanzadas en las solicitudes.", + "components.PermissionEdit.advancedrequest": "Solicitudes Avanzadas", "components.PermissionEdit.adminDescription": "Acceso completo de administrador. Ignora otras comprobaciones de permisos.", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envía notificaciones cuando los usuarios solicitan nuevos contenidos que se aprueban automáticamente.", "components.NotificationTypeSelector.mediaAutoApproved": "Contenidos Aprobados Automáticamente", "components.MovieDetails.playonplex": "Ver en Plex", "components.MovieDetails.play4konplex": "Ver en Plex en 4K", - "components.MovieDetails.openradarr4k": "Abrir Película 4K en Radarr", - "components.MovieDetails.openradarr": "Abrir Película en Radarr", "components.MovieDetails.markavailable": "Marcar como Disponible", "components.MovieDetails.mark4kavailable": "Marcar como Disponible en 4K", - "components.MovieDetails.downloadstatus": "Estado de Descarga", "components.Login.forgotpassword": "¿Contraseña olvidada?", "components.Discover.upcomingtv": "Próximas Series", "components.Discover.TvGenreSlider.tvgenres": "Géneros de Series", "components.Discover.StudioSlider.studios": "Estudios", "components.Discover.NetworkSlider.networks": "Cadenas de TV", "components.Discover.MovieGenreSlider.moviegenres": "Géneros de Películas", - "components.CollectionDetails.requestswillbecreated4k": "Los siguientes títulos tendrán peticiones en 4K:", "components.CollectionDetails.requestcollection4k": "Pedir Colección en 4K", "components.AppDataWarning.dockerVolumeMissingDescription": "El montaje del volumen {appDataPath} no se ha configurado correctamente. Todos los datos se eliminarán cuando el contenedor se pare o reinicie.", "components.Settings.SettingsUsers.defaultPermissions": "Permisos por Defecto", @@ -489,7 +472,6 @@ "components.ResetPassword.requestresetlinksuccessmessage": "Un enlace para reiniciar la contraseña se enviará a la dirección de email indicada si está asociada a un usuario valido.", "components.ResetPassword.password": "Contraseña", "components.ResetPassword.gobacklogin": "Volver a la Página de Login", - "components.RequestModal.requestall": "Pedir Todas las Temporadas", "components.RequestModal.alreadyrequested": "Ya Pedida", "components.Discover.TvGenreList.seriesgenres": "Géneros de Series", "components.Discover.MovieGenreList.moviegenres": "Géneros de Películas", @@ -510,31 +492,24 @@ "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Tipo de Cuenta", "components.UserProfile.ProfileHeader.userid": "Id de Usuario: {userid}", "components.UserProfile.ProfileHeader.settings": "Editar Ajustes", - "components.UserProfile.ProfileHeader.profile": "Ver PErfil", + "components.UserProfile.ProfileHeader.profile": "Ver Perfil", "components.UserProfile.ProfileHeader.joindate": "Unido el {joindate}", "components.UserList.validationEmail": "Debes indicar un dirección de email válida", "components.UserList.userssaved": "¡Permisos de usuario guardados con éxito!", "components.UserList.users": "Usuarios", "components.UserList.userfail": "Algo fue mal al guardar los permisos del usuario.", - "components.UserList.sortUpdated": "Última Actualización", - "components.UserList.sortRequests": "Número de Peticiones", + "components.UserList.sortRequests": "Número de Solicitudes", "components.UserList.sortDisplayName": "Nombre a mostrar", - "components.UserList.sortCreated": "Fecha de Creación", + "components.UserList.sortCreated": "Fecha de Unión", "components.UserList.owner": "Propietario", "components.UserList.edituser": "Editar Permisos de Usuario", "components.UserList.bulkedit": "Edición Masiva", "components.UserList.accounttype": "Tipo", "components.TvDetails.playonplex": "Ver en Plex", - "components.TvDetails.opensonarr4k": "Abrir Serie 4K en Sonarr", "components.TvDetails.play4konplex": "Ver en Plex en 4K", - "components.TvDetails.opensonarr": "Abrir Serie en Sonarr", "components.TvDetails.nextAirDate": "Próxima Emisión", - "components.TvDetails.markavailable": "Marcar como Disponible", - "components.TvDetails.mark4kavailable": "Marcar como Disponible en 4K", "components.TvDetails.episodeRuntimeMinutes": "{runtime} minutos", "components.TvDetails.episodeRuntime": "Duración del Episodio", - "components.TvDetails.downloadstatus": "Estado de la Descarga", - "components.TvDetails.allseasonsmarkedavailable": "* Todas las temporadas se marcarán como disponibles.", "components.Setup.setup": "Configuración", "components.Setup.scanbackground": "El escaneo seguirá en segundo plano. Puedes continuar mientras el proceso de configuración.", "components.Settings.webhook": "Webhook", @@ -561,7 +536,7 @@ "components.Settings.regionTip": "Filtrar contenido por disponibilidad regional", "components.Settings.region": "Región para la sección \"Descubrir\"", "components.Settings.originallanguage": "Idioma para la sección Descubrir", - "components.Settings.partialRequestsEnabled": "Permitir Peticiones Parciales de Series", + "components.Settings.partialRequestsEnabled": "Permitir Solicitudes Parciales de Series", "components.Settings.originallanguageTip": "Filtrar contenido por idioma original", "components.Settings.notificationAgentSettingsDescription": "Configura y habilita los agentes de notificaciones.", "components.Settings.menuUsers": "Usuarios", @@ -638,8 +613,8 @@ "i18n.usersettings": "Ajustes de Usuario", "i18n.settings": "Ajustes", "i18n.advanced": "Avanzado", - "components.UserProfile.recentrequests": "Peticiones Recientes", - "components.UserProfile.norequests": "Sin peticiones.", + "components.UserProfile.recentrequests": "Solicitudes Recientes", + "components.UserProfile.norequests": "Sin solicitudes.", "components.UserProfile.UserSettings.menuPermissions": "Permisos", "components.UserProfile.UserSettings.menuNotifications": "Notificaciones", "components.UserProfile.UserSettings.menuGeneralSettings": "General", @@ -691,26 +666,26 @@ "i18n.areyousure": "¿Estás Seguro?", "i18n.all": "Todas", "components.UserProfile.unlimited": "Ilimitadas", - "components.UserProfile.totalrequests": "Peticiones Totales", - "components.UserProfile.seriesrequest": "Peticiones de Series", + "components.UserProfile.totalrequests": "Solicitudes Totales", + "components.UserProfile.seriesrequest": "Solicitudes de Series", "components.UserProfile.requestsperdays": "{limit} restantes", "components.UserProfile.pastdays": "{type} (últimos {days} días)", - "components.UserProfile.movierequests": "Peticiones de Películas", + "components.UserProfile.movierequests": "Solicitudes de Películas", "components.UserProfile.limit": "{remaining} de {limit}", - "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Límite de Peticiones de Series", - "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Límite de Peticiones de Películas", + "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Límite de Solicitudes de Series", + "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Límite de Solicitudes de Películas", "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Límite global de Sobreescritura", "components.TvDetails.originaltitle": "Título Original", - "components.Settings.SettingsUsers.tvRequestLimitLabel": "Límite Global de Peticiones de Series", - "components.Settings.SettingsUsers.movieRequestLimitLabel": "Límite Global de Peticiones de Películas", + "components.Settings.SettingsUsers.tvRequestLimitLabel": "Límite Global de Solicitudes de Series", + "components.Settings.SettingsUsers.movieRequestLimitLabel": "Límite Global de Solicitudes de Películas", "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {temporada} other {temporadas}}", "components.RequestModal.QuotaDisplay.season": "temporada", - "components.RequestModal.QuotaDisplay.requiredquotaUser": "Este usuario necesita tener al menos {seasons} {seasons, plural, one {petición de temporada} other {peticiones de temporadas}} restante(s) para poder enviar una petición para esta serie.", - "components.RequestModal.QuotaDisplay.requiredquota": "Necesitas tener al menos {seasons} {seasons, plural, one {petición de temporada} other {peticiones de temporadas}} restante(s) para poder enviar una petición para esta serie.", - "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {No} other {#}} {type} {remaining, plural, one {petición} other {peticiones}} restante(s)", - "components.RequestModal.QuotaDisplay.quotaLinkUser": "Puedes ver un resumen de los límites de peticiones de estos usuarios en sus páginas de perfil.", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "Este usuario necesita tener al menos {seasons} {seasons, plural, one {solicitud de temporada} other {solicitudes de temporadas}} restante(s) para poder enviar una solicitud para esta serie.", + "components.RequestModal.QuotaDisplay.requiredquota": "Necesitas tener al menos {seasons} {seasons, plural, one {solicitud de temporada} other {solicitudes de temporadas}} restante(s) para poder enviar una solicitud para esta serie.", + "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {No} other {#}} {type} {remaining, plural, one {solicitud} other {solicitudes}} restante(s)", + "components.RequestModal.QuotaDisplay.quotaLinkUser": "Puedes ver un resumen de los límites de solicitudes de estos usuarios en sus páginas de perfil.", "components.RequestModal.QuotaDisplay.quotaLink": "Puedes ver un resumen de tus límites de peticiones en tu página de perfil.", - "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "No te quedan suficiente peticiones de temporadas restantes", + "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "No te quedan suficientes solicitudes de temporadas restantes", "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {película} other {películas}}", "components.RequestModal.QuotaDisplay.movie": "película", "components.RequestModal.QuotaDisplay.allowedRequestsUser": "Este usuario tiene permitido {limit} {type} cada {days} días.", @@ -732,7 +707,7 @@ "components.UserProfile.UserSettings.UserNotificationSettings.email": "Email", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "¡Los ajustes de notificaciones de Discord se han guardado correctamente!", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "No se han podido guardar los ajustes de notificaciones de Discord.", - "components.Settings.serviceSettingsDescription": "Configura tu(s) servidor(es) {serverType} a continuación. Puedes conectar a múltiples servidores de {serverType}, pero solo dos de ellos pueden ser marcados como por defecto (uno no-4k y otro 4k). Los administrador podrán modificar el servidor usado al procesar nuevas peticiones antes de su aprobación.", + "components.Settings.serviceSettingsDescription": "Configura tu(s) servidor(es) {serverType} a continuación. Puedes conectar a múltiples servidores de {serverType}, pero solo dos de ellos pueden ser marcados como por defecto (uno no-4k y otro 4k). Los administradores podrán modificar el servidor usado al procesar nuevas solicitudes antes de su aprobación.", "components.Settings.mediaTypeMovie": "película", "components.Settings.SonarrModal.testFirstTags": "Probar conexión para cargar etiquetas", "components.Settings.SonarrModal.tags": "Etiquetas", @@ -743,7 +718,7 @@ "components.Settings.SonarrModal.default4kserver": "Servidor 4K por defecto", "components.Settings.SonarrModal.create4ksonarr": "Añadir nuevo servidor Sonarr 4K", "components.Settings.SonarrModal.animeTags": "Etiquetas Anime", - "components.Settings.noDefaultServer": "Al menos un servidor {serverType} debe marcarse como por defecto para que las peticiones de {mediaType} sean procesadas.", + "components.Settings.noDefaultServer": "Al menos un servidor {serverType} debe marcarse como por defecto para que las solicitudes de {mediaType} sean procesadas.", "components.Settings.noDefaultNon4kServer": "Si solo tienes un único servidor {serverType} para contenidos 4K y no 4K (o si solo descargas contenidos 4k), tu servidor {serverType} NO debería marcarse como un servidor 4k.", "components.Settings.mediaTypeSeries": "serie", "components.Settings.SettingsAbout.uptodate": "Actualizado", @@ -759,34 +734,34 @@ "components.Settings.Notifications.validationPgpPrivateKey": "Debes indicar una clave privada PGP", "components.Settings.Notifications.validationPgpPassword": "Debes indicar una contraseña PGP", "components.Settings.Notifications.botUsernameTip": "Permite a los usuarios iniciar también un chat con tu bot y configurar sus propias notificaciones", - "components.RequestModal.pendingapproval": "Tu petición está pendiente de aprobación.", + "components.RequestModal.pendingapproval": "Tu solicitud está pendiente de aprobación.", "components.RequestModal.AdvancedRequester.tags": "Etiquetas", "components.RequestModal.AdvancedRequester.selecttags": "Seleccionar etiquetas", "components.RequestModal.AdvancedRequester.notagoptions": "Sin etiquetas.", - "components.RequestList.RequestItem.mediaerror": "El título asociado a esta petición ya no está disponible.", - "components.RequestList.RequestItem.deleterequest": "Borrar Petición", - "components.RequestList.RequestItem.cancelRequest": "Cancelar Petición", - "components.RequestCard.mediaerror": "El título asociado a esta petición ya no está disponible.", - "components.RequestCard.deleterequest": "Borrar Petición", + "components.RequestList.RequestItem.mediaerror": "El título asociado a esta solicitud ya no está disponible.", + "components.RequestList.RequestItem.deleterequest": "Borrar Solicitud", + "components.RequestList.RequestItem.cancelRequest": "Cancelar Solicitud", + "components.RequestCard.mediaerror": "El título asociado a esta solicitud ya no está disponible.", + "components.RequestCard.deleterequest": "Borrar Solicitud", "components.NotificationTypeSelector.notificationTypes": "Tipos de Notificación", "components.Layout.VersionStatus.streamstable": "Overseer (Estable)", "components.Layout.VersionStatus.streamdevelop": "Overseer (Desarrollo)", "components.Layout.VersionStatus.outofdate": "Desactualizado", - "components.Discover.noRequests": "Sin peticiones.", + "components.Discover.noRequests": "Sin solicitudes.", "components.UserList.autogeneratepasswordTip": "Envía por email una contraseña al usuario generada por el servidor", "i18n.retrying": "Reintentando…", "components.Settings.serverSecure": "seguro", "components.UserList.usercreatedfailedexisting": "La dirección de email proporcionada ya está en uso por otro usuario.", "components.Settings.SonarrModal.enableSearch": "Habilitar Búsqueda Automática", "components.Settings.RadarrModal.enableSearch": "Habilitar Búsqueda Automática", - "components.RequestModal.edit": "Editar petición", - "components.RequestList.RequestItem.editrequest": "Editar petición", + "components.RequestModal.edit": "Editar Solicitud", + "components.RequestList.RequestItem.editrequest": "Editar Solicitud", "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "¡Ajustes de notificacion de Web Push guardados con éxito!", "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Fallo al guardar los ajustes de notificaciones de Web Push.", "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Mostrar Idioma", "components.Settings.webpush": "Web Push", - "components.Settings.noDefault4kServer": "Un servidor 4K de {serverType} debe ser marcado por defecto para poder habilitar las peticiones 4K de {mediaType} de los usuarios.", + "components.Settings.noDefault4kServer": "Un servidor 4K de {serverType} debe ser marcado por defecto para poder habilitar las solicitudes 4K de {mediaType} de los usuarios.", "components.Settings.is4k": "4K", "components.Settings.SettingsUsers.newPlexLoginTip": "Habilitar inicio de sesión de usuarios de Plex sin importarse previamente", "components.Settings.SettingsUsers.newPlexLogin": "Habilitar nuevo inicio de sesión de Plex", @@ -824,23 +799,23 @@ "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "Fallo al enviar la notificación de prueba de LunaSea.", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "¡Los ajustes de notificación se han guardado con éxito!", "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "Fallo al guardar los ajustes de notificación de LunaSea.", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Requerido solo si no se usa el perfil por defecto", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Requerido solo si no se usa el perfil por default", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Nombre de Perfil", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Habilitar Agente", - "components.PermissionEdit.requestTvDescription": "Conceder permisos para solicitar series no en 4k.", + "components.PermissionEdit.requestTvDescription": "Conceder permisos para solicitar series que no sean 4k.", "components.PermissionEdit.requestTv": "Solicitar Series", - "components.PermissionEdit.requestMoviesDescription": "Conceder permisos para solicitar películas no en 4K.", + "components.PermissionEdit.requestMoviesDescription": "Conceder permisos para solicitar películas que no sean 4K.", "components.PermissionEdit.requestMovies": "Solicitar películas", "components.Settings.SettingsAbout.betawarning": "¡Este es un software BETA. Algunas funcionalidades podrían fallar. Por favor, reporta cualquier problema en Github!", "components.Settings.Notifications.NotificationsLunaSea.validationTypes": "Debes seleccionar, al menos, un tipo de notificacion", "components.RequestList.RequestItem.requesteddate": "Solicitado", - "components.RequestCard.failedretry": "Algo fue mal al reintentar la petición.", - "components.NotificationTypeSelector.usermediarequestedDescription": "Notificar cuando otros usuarios envíen nuevas peticiones que requieran aprobación.", - "components.NotificationTypeSelector.usermediafailedDescription": "Notificar cuando los contenidos pedidos fallen al añadirse a Radarr o Sonarr.", - "components.NotificationTypeSelector.usermediadeclinedDescription": "Notificar cuando los contenidos pedidos sean rechazados.", - "components.NotificationTypeSelector.usermediaavailableDescription": "Notificar cuando los contenidos pedidos estén disponibles.", - "components.NotificationTypeSelector.usermediaapprovedDescription": "Notificar cuando las peticiones sea aprobadas.", - "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Notificar cuando otros usuarios envíen nuevas peticiones que se hayan aprobado automáticamente.", + "components.RequestCard.failedretry": "Algo fue mal al reintentar la solicitud.", + "components.NotificationTypeSelector.usermediarequestedDescription": "Notificar cuando otros usuarios envíen nuevas solicitudes que requieran aprobación.", + "components.NotificationTypeSelector.usermediafailedDescription": "Notificar cuando las solicitudes de contenido fallen al añadirse a Radarr o Sonarr.", + "components.NotificationTypeSelector.usermediadeclinedDescription": "Notificar cuando tus contenidos solicitados sean rechazados.", + "components.NotificationTypeSelector.usermediaavailableDescription": "Notificar cuando tus contenidos solicitados estén disponibles.", + "components.NotificationTypeSelector.usermediaapprovedDescription": "Notificar cuando las solicitudes sean aprobadas.", + "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Notificar cuando otros usuarios envíen nuevas solicitudes que se aprueben automáticamente.", "components.MovieDetails.showmore": "Mostrar más", "components.MovieDetails.showless": "Mostrar menos", "components.Layout.LanguagePicker.displaylanguage": "Mostrar idioma", @@ -888,5 +863,128 @@ "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Algo fue mal al guardar la tarea programada.", "components.Settings.SettingsJobsCache.editJobSchedule": "Modificar tarea programada", "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frecuencia", - "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "¡Tarea programada modificada con éxito!" + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "¡Tarea programada modificada con éxito!", + "components.IssueDetails.IssueComment.areyousuredelete": "¿Estás seguro de querer borrar este comentario?", + "components.IssueDetails.IssueComment.delete": "Borrar Comentario", + "components.IssueDetails.IssueComment.edit": "Modificar Comentario", + "components.IssueDetails.IssueComment.postedby": "Escrito por {username} el {relativeTime}", + "components.IssueDetails.IssueComment.postedbyedited": "Escrito por {username} el {relativeTime} (Modificado)", + "components.IssueDetails.IssueComment.validationComment": "Debes introducir un mensaje", + "components.IssueDetails.allepisodes": "Todos los Episodios", + "components.IssueDetails.problemepisode": "Episodio Afectado", + "components.IssueDetails.IssueDescription.deleteissue": "Borrar Incidencia", + "components.IssueDetails.IssueDescription.description": "Descripción", + "components.IssueDetails.closeissue": "Cerrar Incidencia", + "components.IssueDetails.closeissueandcomment": "Cerrar con Comentarios", + "components.IssueDetails.comments": "Comentarios", + "components.IssueDetails.IssueDescription.edit": "Modificar Descripción", + "components.IssueDetails.allseasons": "Todas las Temporadas", + "components.IssueDetails.deleteissue": "Borrar Incidencia", + "components.IssueDetails.deleteissueconfirm": "¿Estás seguro de querer borrar esta incidencia?", + "components.IssueDetails.episode": "Episodio {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Incidencia", + "components.IssueDetails.issuetype": "Tipo", + "components.IssueDetails.lastupdated": "Última Actualización", + "components.IssueDetails.leavecomment": "Commentario", + "components.IssueDetails.nocomments": "Sin comentarios.", + "components.IssueDetails.openedby": "#{issueId} abierta {relativeTime} por {username}", + "components.IssueDetails.openinarr": "Abierta en {arr}", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Todas las temporadas se marcarán como disponibles.", + "components.ManageSlideOver.movie": "película", + "i18n.open": "Abrir", + "i18n.resolved": "Resuelta", + "components.IssueDetails.toaststatusupdated": "¡Estado de la incidencia actualizado con éxito!", + "components.IssueList.IssueItem.issuestatus": "Estado", + "components.IssueList.IssueItem.opened": "Abierta", + "components.IssueList.issues": "Incidencias", + "components.IssueModal.issueOther": "Otro", + "components.IssueList.showallissues": "Mostrar Todas las Incidencias", + "components.PermissionEdit.manageissuesDescription": "Conceder permiso para gestionar incidencias en contenidos.", + "components.ManageSlideOver.manageModalRequests": "Solicitudes", + "components.IssueList.sortModified": "Última Modificación", + "components.IssueModal.CreateIssueModal.allseasons": "Todas las Temporadas", + "components.IssueModal.CreateIssueModal.issomethingwrong": "¿Hay algún problema con {title}?", + "components.IssueModal.CreateIssueModal.episode": "Episodio {episodeNumber}", + "components.IssueModal.CreateIssueModal.problemseason": "Temporada Afectada", + "components.IssueModal.CreateIssueModal.toastviewissue": "Ver Incidencia", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Debes escribir una descripción", + "components.ManageSlideOver.tvshow": "series", + "components.NotificationTypeSelector.issuecreated": "Incidencia Reportada", + "components.ManageSlideOver.manageModalNoRequests": "Sin solicitudes.", + "components.NotificationTypeSelector.userissuecommentDescription": "Notificar cuando incidencias reportadas por ti reciban nuevos comentarios.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Notificar cuando se resuelvan incidencias reportadas por ti.", + "components.IssueModal.CreateIssueModal.whatswrong": "¿Qué sucede?", + "components.ManageSlideOver.manageModalClearMedia": "Limpiar Datos de los Contenidos", + "components.ManageSlideOver.manageModalTitle": "Gestionar {mediaType}", + "components.ManageSlideOver.markavailable": "Marcar como Disponible", + "components.ManageSlideOver.openarr": "Abrir en {arr}", + "components.NotificationTypeSelector.adminissuecommentDescription": "Notificar cuando otros usuarios comenten incidencias.", + "components.NotificationTypeSelector.issuecomment": "Comentario de Incidencia", + "components.PermissionEdit.createissuesDescription": "Conceder permiso para reportar incidencias en contenidos.", + "components.PermissionEdit.viewissuesDescription": "Conceder permiso para ver incidencias de contenidos reportadas por otros usuarios.", + "components.PermissionEdit.createissues": "Incidencias Reportadas", + "components.PermissionEdit.manageissues": "Gestionar Incidencias", + "components.IssueDetails.problemseason": "Temporada Afectada", + "components.IssueDetails.season": "Temporada {seasonNumber}", + "components.IssueDetails.toaststatusupdatefailed": "Algo fue mal mientras se actualizaba el estado de la incidencia.", + "components.PermissionEdit.viewissues": "Ver Incidencias", + "components.NotificationTypeSelector.issueresolvedDescription": "Enviar notificaciones cuando las incidencias se resuelvan.", + "components.IssueDetails.reopenissueandcomment": "Reabrir con Comentarios", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "¡Los ajustes de notificación de Pushbullet se guardaron con éxito!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Clave de Usuario o Grupo", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Debes indicar una clave válida de usuario o grupo", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Los ajustes de notificación Pushover fallaron al guardar.", + "components.IssueDetails.toasteditdescriptionfailed": "Algo fue mal mientras se modificaba la descripción de la incidencia.", + "components.IssueDetails.toasteditdescriptionsuccess": "¡La descripción de la incidencia se ha modificado correctamente!", + "components.IssueDetails.toastissuedeleted": "¡Incidencia eliminada correctamente!", + "components.IssueDetails.toastissuedeletefailed": "Algo fue mal mientras se eliminaba la incidencia.", + "components.IssueDetails.unknownissuetype": "Desconocido", + "components.IssueList.IssueItem.issuetype": "Tipo", + "components.IssueModal.CreateIssueModal.allepisodes": "Todos los Episodios", + "components.IssueList.sortAdded": "Más Reciente", + "components.IssueList.IssueItem.problemepisode": "Episodio Afectado", + "components.IssueList.IssueItem.openeduserdate": "{date} por {user}", + "components.IssueList.IssueItem.unknownissuetype": "Desconocida", + "components.IssueList.IssueItem.viewissue": "Ver Incidencia", + "components.IssueModal.CreateIssueModal.providedetail": "Por favor envíe una explicación detallada de la incidencia encontrada.", + "components.IssueModal.CreateIssueModal.season": "Temporada {seasonNumber}", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "¡Incidencia reportada por {title} enviada con éxito!", + "components.Layout.Sidebar.issues": "Incidencias", + "components.ManageSlideOver.downloadstatus": "Estado de la Descarga", + "components.IssueModal.CreateIssueModal.problemepisode": "Episodio Afectado", + "components.IssueModal.CreateIssueModal.reportissue": "Reportar Incidencia", + "components.IssueModal.CreateIssueModal.submitissue": "Enviar Incidencia", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Algo fue mal mientras se enviaba la incidencia.", + "components.IssueModal.issueAudio": "Audio", + "components.IssueModal.issueSubtitles": "Subtítulo", + "components.IssueModal.issueVideo": "Vídeo", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Esto eliminará irreversiblemente todos los datos de {mediaType}, incluyendo todas las solicitudes. Si este elemento existe en la biblioteca de Plex, la información de los contenidos se recreará en el siguiente escaneado.", + "components.ManageSlideOver.mark4kavailable": "Marcar como Disponible en 4K", + "components.ManageSlideOver.openarr4k": "Abrir en 4K {arr}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Token de Acceso", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Debes indicar un token de aplicación valido", + "components.NotificationTypeSelector.issuecommentDescription": "Envía notificaciones cuando las incidencias reciben nuevos comentarios.", + "components.NotificationTypeSelector.issuecreatedDescription": "Enviar notificaciones cuando las incidencias sean reportadas.", + "components.NotificationTypeSelector.issueresolved": "Incidencia Resuelta", + "components.NotificationTypeSelector.userissuecreatedDescription": "Notificar cuando otros usuarios reporten incidencias.", + "components.IssueDetails.reopenissue": "Reabrir Incidencia", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "¡Los ajustes de notificación de Pushover se guardaron con éxito!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Crear un token desde tus Ajustes de Cuenta", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Tu identificador de usuario o grupo de 30 caracteres", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Debes indicar un token de acceso", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Los ajustes de Notificación de Pushbullet fallaron al guardar.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registrar una aplicaicón para su uso con {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "API Token de Aplicación", + "components.IssueDetails.openin4karr": "Abrir en {arr} 4K", + "components.IssueDetails.play4konplex": "Ver en 4K en Plex", + "components.IssueDetails.playonplex": "Ver en Plex", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episodio} other {Episodios}}", + "components.IssueModal.CreateIssueModal.extras": "Extras", + "components.ManageSlideOver.manageModalIssues": "Incidencias Abiertas", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Notificar cuando se reabran incidencias por otros usuarios.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Notificar cuando otros usuarios resuelvan incidencias.", + "components.NotificationTypeSelector.issuereopenedDescription": "Enviar notificaciones cuando se reabran incidencias.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Notificar cuando se reabran incidencias reportadas por ti.", + "components.NotificationTypeSelector.issuereopened": "Incidencia Reabierta" } diff --git a/src/i18n/locale/fr.json b/src/i18n/locale/fr.json index 395af4b6f..dc2cf60ba 100644 --- a/src/i18n/locale/fr.json +++ b/src/i18n/locale/fr.json @@ -16,11 +16,6 @@ "components.Layout.UserDropdown.signout": "Se déconnecter", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cast": "Casting", - "components.MovieDetails.manageModalClearMedia": "Effacer les données médias", - "components.MovieDetails.manageModalClearMediaWarning": "* Cette action effacera toutes les données sur ce film de manière irréversible, y compris les demandes. Si cet élément existe dans votre bibliothèque Plex, les informations du média seront recréées au prochain scan.", - "components.MovieDetails.manageModalNoRequests": "Aucune demande.", - "components.MovieDetails.manageModalRequests": "Demandes d'ajout", - "components.MovieDetails.manageModalTitle": "Gérer le film", "components.MovieDetails.originallanguage": "Langue originale", "components.MovieDetails.overview": "Résumé", "components.MovieDetails.overviewunavailable": "Résumé indisponible.", @@ -43,7 +38,7 @@ "components.RequestModal.requestSuccess": "{title} demandé avec succès !", "components.RequestModal.requestadmin": "Cette demande sera validée automatiquement.", "components.RequestModal.requestfrom": "La demande de {username} est en attente de validation.", - "components.RequestModal.requestseasons": "Demander {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}", + "components.RequestModal.requestseasons": "Demander {seasonCount} {seasonCount, plural, one {Saison} other {Saisons}}", "components.RequestModal.requesttitle": "Demander {title}", "components.RequestModal.season": "Saison", "components.RequestModal.seasonnumber": "Saison {number}", @@ -78,7 +73,7 @@ "components.Settings.RadarrModal.toastRadarrTestFailure": "Échec de la connexion à Radarr.", "components.Settings.RadarrModal.toastRadarrTestSuccess": "Connexion avec le Serveur Radarr établie avec succès !", "components.Settings.RadarrModal.validationApiKeyRequired": "Vous devez fournir une clé d'API", - "components.Settings.RadarrModal.validationHostnameRequired": "Vous devez fournir un nom d'hôte ou une adresse IP", + "components.Settings.RadarrModal.validationHostnameRequired": "Vous devez fournir un nom d'hôte ou une adresse IP valide", "components.Settings.RadarrModal.validationPortRequired": "Vous devez fournir un numéro de port valide", "components.Settings.RadarrModal.validationProfileRequired": "Vous devez sélectionner un profil", "components.Settings.RadarrModal.validationRootFolderRequired": "Vous devez sélectionner un dossier racine", @@ -99,7 +94,7 @@ "components.Settings.SonarrModal.servername": "Nom du serveur", "components.Settings.SonarrModal.ssl": "Utiliser SSL", "components.Settings.SonarrModal.validationApiKeyRequired": "Vous devez fournir une clé d'API", - "components.Settings.SonarrModal.validationHostnameRequired": "Vous devez fournir un nom d'hôte ou une adresse IP", + "components.Settings.SonarrModal.validationHostnameRequired": "Vous devez fournir un nom d'hôte ou une adresse IP valide", "components.Settings.SonarrModal.validationPortRequired": "Vous devez fournir un numéro de port valide", "components.Settings.SonarrModal.validationProfileRequired": "Vous devez sélectionner un profil qualité", "components.Settings.SonarrModal.validationRootFolderRequired": "Vous devez sélectionner un dossier racine", @@ -148,19 +143,13 @@ "components.Setup.signinMessage": "Commencez en vous connectant avec votre compte Plex", "components.Setup.welcome": "Bienvenue sur Overseerr", "components.TvDetails.cast": "Casting", - "components.TvDetails.manageModalClearMedia": "Effacer les données médias", - "components.TvDetails.manageModalClearMediaWarning": "* Cette action supprimera irrémédiablement toutes les données pour cette série, y compris toutes les demandes. Si cet élément existe dans votre bibliothèque Plex, les informations du média seront recréées lors du prochain scan.", - "components.TvDetails.manageModalNoRequests": "Aucune demande.", - "components.TvDetails.manageModalRequests": "Demandes", - "components.TvDetails.manageModalTitle": "Gérer les séries", "components.TvDetails.originallanguage": "Langue originale", "components.TvDetails.overview": "Résumé", "components.TvDetails.overviewunavailable": "Résumé indisponible.", "components.TvDetails.recommendations": "Recommandations", "components.TvDetails.similar": "Séries similaires", "components.UserList.admin": "Admin", - "components.UserList.created": "Créé", - "components.UserList.lastupdated": "Mise à jour", + "components.UserList.created": "A rejoint", "components.UserList.plexuser": "Utilisateur Plex", "components.UserList.role": "Rôle", "components.UserList.totalrequests": "Requêtes", @@ -193,7 +182,7 @@ "components.Settings.SettingsAbout.version": "Version", "components.Settings.SettingsAbout.totalrequests": "Total des demandes", "components.Settings.SettingsAbout.totalmedia": "Total des médias", - "components.Settings.SettingsAbout.overseerrinformation": "Informations sur Overseerr", + "components.Settings.SettingsAbout.overseerrinformation": "À propos d'Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "Discussions GitHub", "components.Settings.SettingsAbout.gettingsupport": "Obtenir de l'aide", "components.Settings.RadarrModal.validationNameRequired": "Vous devez fournir un nom de serveur", @@ -228,11 +217,11 @@ "components.Settings.SettingsAbout.helppaycoffee": "Aidez-nous à payer le café", "components.Settings.SettingsAbout.Releases.viewongithub": "Voir sur GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Voir le journal des modifications", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Journal des modifications de version", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Journal des modifications de la version {version}", "components.Settings.SettingsAbout.Releases.releases": "Versions", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Données de sortie indisponibles. GitHub est-il en panne ?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Les données de version sont actuellement indisponible.", "components.Settings.SettingsAbout.Releases.latestversion": "Dernière version", - "components.Settings.SettingsAbout.Releases.currentversion": "Version actuelle", + "components.Settings.SettingsAbout.Releases.currentversion": "Actuelle", "components.UserList.importfromplexerror": "Une erreur s'est produite durant l'importation d'utilisateurs depuis Plex.", "components.UserList.importfromplex": "Importer des utilisateurs depuis Plex", "components.UserList.importedfromplex": "{userCount, plural, one {# nouvel utilisateur} other {# nouveaux utilisateurs}} importé(s) depuis Plex avec succès !", @@ -245,9 +234,7 @@ "components.Settings.Notifications.allowselfsigned": "Autoriser les certificats autosignés", "components.TvDetails.watchtrailer": "Regarder la bande-annonce", "components.MovieDetails.watchtrailer": "Regarder la bande-annonce", - "components.CollectionDetails.requestswillbecreated": "Des demandes seront créées pour les titres suivants :", "components.CollectionDetails.requestcollection": "Demander la collection", - "components.CollectionDetails.requestSuccess": "{title} demandé avec succès !", "components.CollectionDetails.overview": "Résumé", "components.CollectionDetails.numberofmovies": "{count} Films", "i18n.requested": "Demandé", @@ -286,7 +273,7 @@ "components.Settings.Notifications.NotificationsPushover.agentenabled": "Activer l'agent", "components.Settings.Notifications.NotificationsPushover.accessToken": "Clé API d'application", "components.RequestList.sortModified": "Dernière modification", - "components.RequestList.sortAdded": "Date de la demande", + "components.RequestList.sortAdded": "Plus récents", "components.RequestList.showallrequests": "Afficher toutes les demandes", "components.StatusBadge.status4k": "{status} en 4K", "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Paramètres de notification Webhook enregistrés avec succès !", @@ -364,27 +351,27 @@ "components.UserList.bulkedit": "Modification en masse", "components.Settings.csrfProtectionTip": "Définir l'accès à l'API externe en lecture seule (nécessite HTTPS et Overseerr doit être rechargé pour que les modifications prennent effet)", "components.Settings.csrfProtection": "Activer la protection CSRF", - "components.PermissionEdit.usersDescription": "Accorder l'autorisation de gérer les utilisateurs d'Overseerr. Les utilisateurs disposant de cette autorisation ne peuvent pas modifier les utilisateurs dotés de privilèges d'administrateur ni les accorder.", + "components.PermissionEdit.usersDescription": "Accorder l'autorisation de gérer les utilisateurs. Les utilisateurs disposant de cette autorisation ne peuvent pas modifier les utilisateurs dotés de privilèges d'administrateur ni les accorder.", "components.PermissionEdit.users": "Gérer les utilisateurs", - "components.PermissionEdit.settingsDescription": "Accorde la permission de modifier les paramètres d'Overseerr. Un utilisateur doit avoir cette autorisation pour l'accorder à d'autres.", + "components.PermissionEdit.settingsDescription": "Accorde la permission de modifier les paramètres globaux. Un utilisateur doit avoir cette autorisation pour l'accorder à d'autres.", "components.PermissionEdit.settings": "Gérer les paramètres", "components.PermissionEdit.requestDescription": "Accorde la permission de demander des médias non-4K.", - "components.PermissionEdit.request4kTvDescription": "Accorde l'autorisation de demander des séries 4K.", + "components.PermissionEdit.request4kTvDescription": "Accorde l'autorisation de demander des séries en 4K.", "components.PermissionEdit.request4kTv": "Demande de séries 4K", - "components.PermissionEdit.request4kMoviesDescription": "Accorder l'autorisation de demander des films 4K.", + "components.PermissionEdit.request4kMoviesDescription": "Accorder la permission de soumettre des demandes de films en 4K.", "components.PermissionEdit.request4kMovies": "Demande de films 4K", - "components.PermissionEdit.request4kDescription": "Accorde la permission de demander des médias 4K.", + "components.PermissionEdit.request4kDescription": "Accorde la permission de soumettre des demandes pour des médias en 4K.", "components.PermissionEdit.request4k": "Demande 4K", "components.PermissionEdit.request": "Demande", - "components.PermissionEdit.managerequestsDescription": "Accorde l'autorisation de gérer les demandes d'Overseerr. Toutes les demandes faites par un utilisateur avec cette autorisation seront automatiquement approuvées.", + "components.PermissionEdit.managerequestsDescription": "Accorde la permission de gérer les demandes de média. Toutes les demandes faites par un utilisateur avec cette autorisation seront automatiquement approuvées.", "components.PermissionEdit.managerequests": "Gérer les demandes", "components.PermissionEdit.autoapproveSeriesDescription": "Accorde la validation automatique pour toutes les demandes de série non-4K.", "components.PermissionEdit.autoapproveSeries": "Validation automatique des séries", "components.PermissionEdit.autoapproveMoviesDescription": "Accorde la validation automatique des demandes de films non-4K.", "components.PermissionEdit.autoapproveMovies": "Validation automatique des films", - "components.PermissionEdit.autoapproveDescription": "Accorde la validation automatique pour toutes les demandes non-4K.", + "components.PermissionEdit.autoapproveDescription": "Accorde la validation automatique pour toutes les demandes de média non-4K.", "components.PermissionEdit.autoapprove": "Validation automatique", - "components.PermissionEdit.advancedrequestDescription": "Accorde la permission d'utiliser les options de demande avancées.", + "components.PermissionEdit.advancedrequestDescription": "Accorde la permission de modifier les options de demande de média avancées.", "components.PermissionEdit.advancedrequest": "Demandes avancées", "components.PermissionEdit.adminDescription": "Accès administrateur complet. Contourne toutes les autres permissions (sélectionnées ou non).", "components.PermissionEdit.admin": "Admin", @@ -403,12 +390,6 @@ "components.Settings.serverLocal": "local", "components.TvDetails.playonplex": "Lire sur Plex", "components.TvDetails.play4konplex": "Lire en 4K sur Plex", - "components.TvDetails.opensonarr4k": "Ouvrir la série dans Sonarr 4K", - "components.TvDetails.opensonarr": "Ouvrir la série dans Sonarr", - "components.TvDetails.markavailable": "Marquer comme disponible", - "components.TvDetails.mark4kavailable": "Marquer comme disponible en 4K", - "components.TvDetails.downloadstatus": "État du téléchargement", - "components.TvDetails.allseasonsmarkedavailable": "* Toutes les saisons seront marquées comme disponibles.", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Connexion à Sonarr établie avec succès !", "components.Settings.SonarrModal.toastSonarrTestFailure": "Échec de la connexion à Sonarr.", "components.Settings.SonarrModal.syncEnabled": "Activer les scans", @@ -419,9 +400,6 @@ "components.MovieDetails.mark4kavailable": "Marquer comme Disponible en 4K", "components.MovieDetails.playonplex": "Lire sur Plex", "components.MovieDetails.play4konplex": "Lire en 4K sur Plex", - "components.MovieDetails.openradarr4k": "Ouvrir le film dans Radarr 4K", - "components.MovieDetails.openradarr": "Ouvrir le film dans Radarr", - "components.MovieDetails.downloadstatus": "État du téléchargement", "components.Settings.trustProxyTip": "Permettre Overseerr à enregistrer correctement les adresses IP des clients derrière un proxy (Overseerr doit être rechargé pour que les modifications prennent effet)", "components.Settings.trustProxy": "Activer la prise en charge proxy", "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr effectue certaines tâches de maintenance comme des tâches planifiées régulièrement, mais elles peuvent également être déclenchées manuellement ci-dessous. L'exécution manuelle d'une tâche ne modifiera pas sa planification.", @@ -494,15 +472,14 @@ "components.RequestModal.AdvancedRequester.languageprofile": "Profil de langue", "components.Settings.Notifications.sendSilentlyTip": "Envoyer des notifications sans son", "components.Settings.Notifications.sendSilently": "Envoyer silencieusement", - "components.UserList.sortUpdated": "Dernière Mise à Jour", "components.UserList.sortRequests": "Nombre de demandes", "components.UserList.sortDisplayName": "Nom d'Utilisateur affiché", - "components.UserList.sortCreated": "Date de création", + "components.UserList.sortCreated": "Date d'inscription", "components.PermissionEdit.autoapprove4kSeriesDescription": "Accorde la validation automatique des demandes de séries 4K faites.", "components.PermissionEdit.autoapprove4kSeries": "Validation automatique des séries 4K", "components.PermissionEdit.autoapprove4kMoviesDescription": "Accorde la validation automatique des demandes de films 4K.", "components.PermissionEdit.autoapprove4kMovies": "Validation automatique des films 4K", - "components.PermissionEdit.autoapprove4kDescription": "Accorde la validation automatique pour toutes les demandes 4K.", + "components.PermissionEdit.autoapprove4kDescription": "Accorde la validation automatique pour toutes les demandes de média en 4K.", "components.PermissionEdit.autoapprove4k": "Validation automatique 4K", "components.AppDataWarning.dockerVolumeMissingDescription": "Le montage du volume {appDataPath} n'a pas été configuré correctement. Toutes les données seront effacées lorsque le conteneur est arrêté ou redémarré.", "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "Identifiant", @@ -545,7 +522,6 @@ "components.UserList.userfail": "Un problème est survenu lors de l'enregistrement des permissions de l'utilisateur.", "components.UserList.edituser": "Modifier les permissions de l'utilisateur", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Paramètres de notification Pushbullet enregistrés avec succès !", - "components.CollectionDetails.requestswillbecreated4k": "Des demandes en 4K seront créés pour les titres suivants :", "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirmez le mot de passe", "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filtrer le contenu par disponibilité régionale", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Région à découvrir", @@ -621,7 +597,6 @@ "components.Settings.Notifications.pgpPassword": "PGP mot de passe", "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", "components.Settings.partialRequestsEnabled": "Permettre les demandes partielles des séries", - "components.RequestModal.requestall": "Demander toutes les saisons", "components.RequestModal.alreadyrequested": "Déjà demandé", "components.Discover.TvGenreSlider.tvgenres": "Genres de séries", "components.Discover.TvGenreList.seriesgenres": "Genres de séries", @@ -881,5 +856,135 @@ "components.TvDetails.streamingproviders": "Disponible en streaming sur", "components.MovieDetails.streamingproviders": "Actuellement diffusé sur", "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Créer une Webhook entrant intégration", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Toutes les {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}", + "components.IssueDetails.IssueComment.areyousuredelete": "Êtes-vous sûr de vouloir supprimer ce commentaire ?", + "components.IssueDetails.IssueComment.postedby": "Posté {relativeTime} par {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Posté {relativeTime} par {username} (Edité)", + "components.IssueDetails.IssueComment.validationComment": "Vous devez écrire un message", + "components.IssueDetails.IssueComment.delete": "Supprimer le commentaire", + "components.IssueDetails.IssueComment.edit": "Éditer le commentaire", + "components.IssueDetails.nocomments": "Aucun commentaires.", + "components.IssueDetails.openedby": "#{issueId} ouvert {relativeTime} par {username}", + "components.IssueDetails.problemepisode": "Épisode concerné", + "components.IssueDetails.problemseason": "Saison concernée", + "components.IssueDetails.reopenissue": "Rouvrir le problème", + "components.IssueDetails.reopenissueandcomment": "Rouvrir avec un commentaire", + "components.IssueDetails.season": "Saison {seasonNumber}", + "components.IssueModal.CreateIssueModal.episode": "Épisode {episodeNumber}", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Y a-t-il un problème avec {title} ?", + "components.IssueModal.CreateIssueModal.problemepisode": "Épisode concerné", + "components.IssueModal.CreateIssueModal.problemseason": "Saison affectée", + "components.IssueModal.CreateIssueModal.providedetail": "Fournissez une explication détaillée du problème.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Recevez une notification lorsque les problèmes que vous avez signalés sont résolus.", + "components.PermissionEdit.manageissues": "Gérer les problèmes", + "components.PermissionEdit.viewissues": "Afficher les problèmes", + "components.PermissionEdit.viewissuesDescription": "Accorder la permission de consulter les problèmes liés aux médias signalés par d'autres utilisateurs.", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Un problème est survenu lors de la soumission du problème.", + "components.IssueModal.CreateIssueModal.toastviewissue": "Afficher le problème", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Le signalement du problème pour {title} a bien été effectué !", + "components.IssueModal.CreateIssueModal.whatswrong": "Qu’est-ce qui ne va pas ?", + "components.Layout.Sidebar.issues": "Problèmes", + "components.ManageSlideOver.downloadstatus": "État du téléchargement", + "components.ManageSlideOver.manageModalNoRequests": "Aucune demandes.", + "components.ManageSlideOver.manageModalRequests": "Demandes", + "components.ManageSlideOver.manageModalTitle": "Gérer {mediaType}", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Ceci supprimera de manière irréversible toutes les données de ce {mediaType}, y compris les demandes éventuelles. Si cet élément existe dans votre bibliothèque Plex, les informations sur le média seront recréées lors de la prochaine analyse.", + "components.ManageSlideOver.tvshow": "séries", + "components.NotificationTypeSelector.issuecomment": "Les commentaires du problème", + "components.NotificationTypeSelector.issuecreatedDescription": "Envoyez des notifications lorsque des problèmes sont signalés.", + "components.PermissionEdit.createissues": "Signaler des problèmes", + "components.PermissionEdit.createissuesDescription": "Accordez la permission de signaler les problèmes liés aux médias.", + "i18n.resolved": "Résolu", + "components.IssueDetails.toasteditdescriptionfailed": "Un problème est survenu lors de l'édition de la description du problème.", + "components.IssueDetails.toastissuedeletefailed": "Un problème est survenu lors de la suppression du problème.", + "components.IssueDetails.allseasons": "Toutes les saisons", + "components.IssueDetails.IssueDescription.description": "Description", + "components.IssueDetails.IssueDescription.edit": "Éditer la description", + "components.IssueDetails.allepisodes": "Tous les épisodes", + "components.IssueDetails.closeissue": "Clôturer le problème", + "components.IssueDetails.IssueDescription.deleteissue": "Supprimer le problème", + "components.IssueDetails.episode": "Épisode {episodeNumber}", + "components.IssueDetails.closeissueandcomment": "Clôturer avec un commentaire", + "components.IssueDetails.lastupdated": "Dernière mise à jour", + "components.IssueDetails.comments": "Commentaires", + "components.IssueDetails.deleteissueconfirm": "Êtes-vous sûr de vouloir supprimer ce problème ?", + "components.IssueDetails.issuepagetitle": "Problème", + "components.IssueDetails.issuetype": "Type", + "components.IssueDetails.deleteissue": "Supprimer le problème", + "components.IssueDetails.leavecomment": "Commenter", + "components.IssueDetails.openinarr": "Ouvrir dans {arr}", + "components.IssueDetails.toasteditdescriptionsuccess": "La modification de la description du problème a bien été réalisée !", + "components.IssueDetails.toaststatusupdated": "Le statut du problème a bien été mis à jour !", + "components.IssueDetails.toastissuedeleted": "Le problème a été supprimé avec succès !", + "components.IssueDetails.toaststatusupdatefailed": "Un problème est survenu lors de la mise à jour du statut du problème.", + "components.IssueDetails.unknownissuetype": "Inconnu", + "components.IssueList.IssueItem.viewissue": "Afficher le problème", + "components.IssueList.sortAdded": "Plus récents", + "components.IssueList.sortModified": "Dernière modification", + "components.NotificationTypeSelector.userissuecommentDescription": "Recevez une notification lorsque les problèmes que vous avez signalés reçoivent de nouveaux commentaires.", + "components.IssueList.IssueItem.issuetype": "Type", + "components.IssueList.IssueItem.issuestatus": "Statut", + "components.IssueList.IssueItem.opened": "Ouvert", + "components.IssueList.IssueItem.problemepisode": "Épisode concerné", + "components.IssueList.issues": "Problèmes", + "components.IssueList.IssueItem.openeduserdate": "{date} par {user}", + "components.IssueList.IssueItem.unknownissuetype": "Inconnu", + "components.IssueList.showallissues": "Afficher tous les problèmes", + "components.IssueModal.CreateIssueModal.allepisodes": "Tous les épisodes", + "components.ManageSlideOver.manageModalClearMedia": "Effacer les données du média", + "components.ManageSlideOver.movie": "film", + "components.IssueModal.CreateIssueModal.allseasons": "Toutes les saisons", + "components.IssueModal.CreateIssueModal.season": "Saison {seasonNumber}", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Vous devez fournir une description", + "components.IssueModal.CreateIssueModal.reportissue": "Signaler un problème", + "components.IssueModal.CreateIssueModal.submitissue": "Soumettre le problème", + "components.IssueModal.issueVideo": "Vidéo", + "components.IssueModal.issueAudio": "Audio", + "components.IssueModal.issueSubtitles": "Sous-titre", + "components.IssueModal.issueOther": "Autre", + "components.ManageSlideOver.mark4kavailable": "Marquer comme disponible en 4K", + "components.ManageSlideOver.markavailable": "Marquer comme disponible", + "components.Settings.SettingsAbout.runningDevelop": "Vous utilisez la branche develop d'Overseerr, qui n'est recommandée que pour ceux qui contribuent au développement ou qui aident aux tests et correctifs.", + "components.ManageSlideOver.openarr": "Ouvrir dans {arr}", + "components.NotificationTypeSelector.adminissuecommentDescription": "Soyez averti lorsque d'autres utilisateurs commentent sur les problèmes.", + "components.NotificationTypeSelector.issuecommentDescription": "Envoyez des notifications lorsque les problèmes reçoivent de nouveaux commentaires.", + "components.NotificationTypeSelector.issueresolved": "Problème résolu", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Toutes les saisons seront marquées comme disponibles.", + "components.ManageSlideOver.openarr4k": "Ouvrir en 4K {arr}", + "components.NotificationTypeSelector.issuecreated": "Problème signalé", + "components.NotificationTypeSelector.issueresolvedDescription": "Envoyez des notifications lorsque les problèmes sont résolus.", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Fréquence", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Tâche modifiée avec succès !", + "i18n.open": "Ouvert", + "components.Settings.SettingsJobsCache.editJobSchedule": "Modifier la tâche", + "components.NotificationTypeSelector.userissuecreatedDescription": "Recevez une notification lorsque d’autres utilisateurs signalent des problèmes.", + "components.PermissionEdit.manageissuesDescription": "Accorder la permission de gérer les problèmes relatifs aux médias.", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Un problème est survenu lors de l'enregistrement de la tâche.", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Toutes les {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}", + "components.IssueDetails.openin4karr": "Ouvrir en 4K {arr}", + "components.IssueDetails.play4konplex": "Lire sur Plex en 4K", + "components.IssueDetails.playonplex": "Lire sur Plex", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Jeton d'accès", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Saison} other {Saisons}}", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Être notifié lorsqu'un problème est ré-ouvert par d'autres utilisateurs.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Être notifié lorsqu'un problème est résolu par d'autres utilisateurs.", + "components.NotificationTypeSelector.issuereopenedDescription": "Envoyer des notifications lorsqu'un problème est ré-ouvert.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Être notifié lorsqu'un problème que vous avez signalé a été ré-ouvert.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Les paramètres de notification Pushbullet n'ont pas été sauvegardés correctement.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Les paramètres de notification Pushbullet ont été sauvegardés correctement !", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Jeton API de l'application", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Enregistrer une application à utiliser avec {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Vous devez fournir un jeton d'application valide", + "components.IssueModal.CreateIssueModal.extras": "Extras", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Les paramètres de notification Pushover n'ont pas été sauvegardés correctement.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Les paramètres de notification Pushover ont été sauvegardés correctement !", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Créer un jeton depuis les paramètres de votre compte", + "components.ManageSlideOver.manageModalIssues": "Problèmes ouverts", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Vous devez fournir une clef d'utilisateur ou de groupe valide", + "components.NotificationTypeSelector.issuereopened": "Problème réouvert", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Vous devez fournir un jeton d'accès", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Épisode} other {Épisodes}}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Clef d'utilisateur ou de groupe", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Votre identifiant d'utilisateur ou de groupe à 30 caractères" } diff --git a/src/i18n/locale/hu.json b/src/i18n/locale/hu.json index 2f15b95dd..752d12e74 100644 --- a/src/i18n/locale/hu.json +++ b/src/i18n/locale/hu.json @@ -57,15 +57,10 @@ "components.MovieDetails.runtime": "{minutes} perc", "components.MovieDetails.revenue": "Bevétel", "components.MovieDetails.releasedate": "Megjelenés dátuma", - "components.MovieDetails.manageModalClearMediaWarning": "* Ezzel véglegesen törlődik az ehhez a filmhez tartozó összes adat, beleértve a hozzá kapcsolódó kéréseket. Ha ez a film létezik a Plex könyvtárban, a média adatok újra létrehozásra kerülnek a következő szinkronizálásnál.", "components.MovieDetails.recommendations": "Ajánlások", "components.MovieDetails.overviewunavailable": "Áttekintés nem elérhető.", "components.MovieDetails.overview": "Áttekintés", "components.MovieDetails.originallanguage": "Eredeti nyelv", - "components.MovieDetails.manageModalTitle": "Film kezelése", - "components.MovieDetails.manageModalRequests": "Kérések", - "components.MovieDetails.manageModalNoRequests": "Nincsenek kérések.", - "components.MovieDetails.manageModalClearMedia": "Összes média adat törlése", "components.MovieDetails.cast": "Szereposztás", "components.MovieDetails.budget": "Költségvetés", "components.MovieDetails.MovieCrew.fullcrew": "Teljes stáb", @@ -96,7 +91,6 @@ "components.Discover.discovertv": "Népszerű sorozatok", "components.Discover.discovermovies": "Népszerű filmek", "components.CollectionDetails.requestcollection": "Gyűjtemény kérése", - "components.CollectionDetails.requestSuccess": "{title} kérés elküldve!", "components.CollectionDetails.overview": "Áttekintés", "components.CollectionDetails.numberofmovies": "Filmek száma: {count}", "components.Search.searchresults": "Keresési találatok", @@ -117,13 +111,9 @@ "components.RequestModal.AdvancedRequester.destinationserver": "Cél szerver", "components.MovieDetails.playonplex": "Lejátszás Plex-en", "components.MovieDetails.play4konplex": "4K lejátszás Plex-en", - "components.MovieDetails.openradarr4k": "Film megnyitása 4K-ban a Radarr-ban", - "components.MovieDetails.openradarr": "Film megnyitása a Radarr-ban", - "components.MovieDetails.downloadstatus": "Letöltés állapota", "components.MovieDetails.markavailable": "Megjelölés elérhetőként", "components.MovieDetails.mark4kavailable": "Megjelölés elérhetőként - 4K", "components.Layout.Sidebar.dashboard": "Felfedezés", - "components.CollectionDetails.requestswillbecreated": "A következő címekhez lesznek kérések létrehozva:", "components.NotificationTypeSelector.mediarequested": "Kérés elküldve", "components.NotificationTypeSelector.mediafailedDescription": "Értesítést küld amikor a kérést nem sikerül hozzáadni a menedzser app-okhoz (Radarr/Sonarr).", "components.NotificationTypeSelector.mediafailed": "Kérés sikertelen", @@ -174,11 +164,10 @@ "components.UserList.passwordinfodescription": "Konfigurálja az alkalmazás URL-jét, és engedélyezze az e-mailes értesítéseket az automatikus jelszógenerálás engedélyezéséhez.", "components.UserList.password": "Jelszó", "components.UserList.localuser": "Helyi felhasználó", - "components.UserList.lastupdated": "Frissítve", "components.UserList.importfromplexerror": "Hiba történt a felhasználók Plex-ről történő importálása közben.", "components.UserList.importfromplex": "Felhasználók importálása Plex-ről", "components.UserList.importedfromplex": "{userCount, plural, =0 {Nem lett új} one {# új} other {# új}} felhasználó importálva Plex-ről!", - "components.UserList.email": "E-mail cím", + "components.UserList.email": "E-mail-cím", "components.UserList.deleteuser": "Felhasználó törlése", "components.UserList.deleteconfirm": "Biztos vagy benne, hogy törlöd ezt a felhasználót? A felhasználó összes adata törlődni fog.", "components.UserList.creating": "Létrehozás…", @@ -198,21 +187,10 @@ "components.TvDetails.overviewunavailable": "Áttekintés nem elérhető.", "components.TvDetails.overview": "Áttekintés", "components.TvDetails.originallanguage": "Eredeti nyelv", - "components.TvDetails.opensonarr4k": "Sorozat megnyitása 4K Sonarr-ban", - "components.TvDetails.opensonarr": "Sorozat megnyitása Sonarr-ban", "components.TvDetails.network": "{networkCount, plural, one {Network} other {Networks}}", - "components.TvDetails.markavailable": "Megjelölés elérhetőként", - "components.TvDetails.mark4kavailable": "Megjelölés elérhetőként - 4K-ban", - "components.TvDetails.manageModalTitle": "Sorozatok kezelése", - "components.TvDetails.manageModalRequests": "Kérések", - "components.TvDetails.manageModalNoRequests": "Nincs kérés.", - "components.TvDetails.manageModalClearMediaWarning": "* Ezzel véglegesen törlődik az ehhez a sorozathoz tartozó összes adat, beleértve a hozzá kapcsolódó kéréseket. Ha ez az sorozat létezik a Plex könyvtárban, a média adatok újra létrehozásra kerülnek a következő szinkronizálásnál.", - "components.TvDetails.manageModalClearMedia": "Média adat törlése", "components.TvDetails.firstAirDate": "Első adás dátuma", - "components.TvDetails.downloadstatus": "Letöltési állapot", "components.TvDetails.cast": "Szereposztás", "components.TvDetails.anime": "Anime", - "components.TvDetails.allseasonsmarkedavailable": "* Az összes évad elérhetőként lesz jelölve.", "components.TvDetails.TvCrew.fullseriescrew": "Sorozat teljes stábja", "components.TvDetails.TvCast.fullseriescast": "Sorozat teljes szereposztása", "components.StatusChacker.reloadOverseerr": "Újratöltés", @@ -276,7 +254,7 @@ "components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Értesítések", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Az e-mail értesítések beállításai sikeresen mentésre kerültek!", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Az e-mail értesítés beállításait nem sikerült elmenteni.", - "components.UserProfile.UserSettings.UserNotificationSettings.email": "E-mail cím", + "components.UserProfile.UserSettings.UserNotificationSettings.email": "E-mail-cím", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord értesítés beállítások sikeresen mentve!", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "A Discord értesítés beállításait nem sikerült elmenteni.", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "A felhasználói fiók azonosítószáma", @@ -310,7 +288,6 @@ "components.UserList.users": "Felhasználók", "components.UserList.userfail": "Valami hiba történt a felhasználói jogosultságok mentése közben.", "components.UserList.usercreatedfailedexisting": "A megadott e-mail címet már egy másik felhasználó használja.", - "components.UserList.sortUpdated": "Utolsó frissítés", "components.UserList.sortRequests": "Kérések száma", "components.UserList.sortDisplayName": "Megjelenített név", "components.UserList.sortCreated": "Létrehozás dátuma", @@ -403,7 +380,7 @@ "components.Settings.generalsettings": "Általános beállítások", "components.Settings.general": "Általános", "components.Settings.enablessl": "Használjon SSL-t", - "components.Settings.email": "E-mail cím", + "components.Settings.email": "E-mail-cím", "components.Settings.deleteserverconfirm": "Biztos, hogy törölni szeretné ezt a szervert?", "components.Settings.default4k": "Alapértelmezett 4K", "components.Settings.default": "Alapértelmezett", @@ -792,7 +769,6 @@ "components.Discover.DiscoverNetwork.networkSeries": "{network} Sorozat", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Filmek", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Filmek", - "components.CollectionDetails.requestswillbecreated4k": "A következő címekhez lesznek 4k kérések létrehozva:", "components.CollectionDetails.requestcollection4k": "Gyűjtemény kérése 4k-ban", "components.AppDataWarning.dockerVolumeMissingDescription": "A {appDataPath} kötet nincs megfelelően csatlakoztatva. A tároló leállításakor vagy újraindításakor minden adat törlődik.", "components.Settings.SonarrModal.rootfolder": "Root Könyvtár", @@ -831,7 +807,6 @@ "components.QuotaSelector.tvRequests": "{quotaLimit} {seasons} per {quotaDays} {days}", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL-je", "components.RequestModal.QuotaDisplay.allowedRequests": "Napi {limit} {type} kérés engedélyezett minden {days} naponta.", - "components.RequestModal.requestall": "Minden évad kérése", "components.Settings.Notifications.NotificationsSlack.agentenabled": "\"ügynök\" engedélyezése", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Meg kell adnia egy érvényes felhasználói vagy csoportos kulcsot", "components.Settings.Notifications.NotificationsPushover.validationTypes": "Legalább egy értesítési típust ki kell választania", diff --git a/src/i18n/locale/it.json b/src/i18n/locale/it.json index 593673b8f..9a17231e7 100644 --- a/src/i18n/locale/it.json +++ b/src/i18n/locale/it.json @@ -37,16 +37,11 @@ "components.MovieDetails.similar": "Titoli simili", "components.MovieDetails.runtime": "{minutes} minuti", "components.MovieDetails.revenue": "Incassi", - "components.MovieDetails.releasedate": "Data di uscita", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Data di rilascio} other {Date di rilascio}}", "components.MovieDetails.recommendations": "Consigliati", "components.MovieDetails.overviewunavailable": "Trama non disponibile.", "components.MovieDetails.overview": "Trama", "components.MovieDetails.originallanguage": "Lingua originale", - "components.MovieDetails.manageModalTitle": "Gestisci film", - "components.MovieDetails.manageModalRequests": "Richieste", - "components.MovieDetails.manageModalNoRequests": "Nessuna richiesta.", - "components.MovieDetails.manageModalClearMediaWarning": "* Questo rimuoverà irreversibilmente tutti i dati per questo film, incluse le richieste. Se questo elemento esiste nella tua libreria Plex, le informazioni saranno ricreate durante la prossima scansione.", - "components.MovieDetails.manageModalClearMedia": "Cancella dati media", "components.MovieDetails.cast": "Cast", "components.MovieDetails.budget": "Budget", "components.MovieDetails.MovieCast.fullcast": "Cast completo", @@ -59,11 +54,10 @@ "components.Discover.upcoming": "Film in uscita", "components.Discover.trending": "Di tendenza", "components.UserList.deleteconfirm": "Sei sicuro di voler rimuovere questo utente? Tutti le richieste verranno rimosse permanentemente.", - "components.UserList.created": "Creato", + "components.UserList.created": "Unito", "components.UserList.admin": "Amministratore", "components.UserList.role": "Ruolo", "components.UserList.plexuser": "Utente Plex", - "components.UserList.lastupdated": "Aggiornato", "components.UserList.deleteuser": "Elimina l'utente", "components.UserList.userlist": "Elenco utenti", "components.UserList.userdeleteerror": "Qualcosa è andato storto nel rimuovere l'utente.", @@ -100,10 +94,6 @@ "components.RequestModal.requestfrom": "La richiesta di {username} è in attesa di approvazione.", "components.RequestModal.pendingrequest": "Richiesta in sospeso per {title}", "components.Layout.Sidebar.dashboard": "Esplora", - "components.TvDetails.manageModalTitle": "Gestisci serie", - "components.TvDetails.manageModalRequests": "Richieste", - "components.TvDetails.manageModalNoRequests": "Nessuna richiesta.", - "components.TvDetails.manageModalClearMedia": "Cancella dati media", "components.TvDetails.cast": "Cast", "components.TvDetails.anime": "Anime", "components.TvDetails.TvCast.fullseriescast": "Cast completo della serie", @@ -145,7 +135,7 @@ "components.Settings.SonarrModal.validationProfileRequired": "È necessario selezionare un profilo di qualità", "components.Settings.SonarrModal.validationPortRequired": "È necessario fornire un numero di porta valido", "components.Settings.SonarrModal.validationNameRequired": "È necessario dare un nome di server", - "components.Settings.SonarrModal.validationHostnameRequired": "È necessario fornire un hostname o un indirizzo IP", + "components.Settings.SonarrModal.validationHostnameRequired": "È necessario fornire un hostname o un indirizzo IP valido", "components.Settings.SonarrModal.validationApiKeyRequired": "È necessario fornire una chiave API", "components.Settings.SonarrModal.testFirstRootFolders": "Testa la connessione per caricare le cartelle", "components.Settings.SonarrModal.testFirstQualityProfiles": "Testa la connessione per caricare profili di qualità", @@ -176,7 +166,7 @@ "components.Settings.RadarrModal.validationPortRequired": "È necessario fornire un numero di porta valido", "components.Settings.RadarrModal.validationNameRequired": "È necessario fornire un nome al server", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "È necessario selezionare una disponibilità minima", - "components.Settings.RadarrModal.validationHostnameRequired": "È necessario fornire un hostname o un indirizzo IP", + "components.Settings.RadarrModal.validationHostnameRequired": "È necessario fornire un hostname o un indirizzo IP valido", "components.Settings.RadarrModal.validationApiKeyRequired": "È necessario fornire una chiave API", "components.Settings.RadarrModal.toastRadarrTestSuccess": "Connessione a Radarr stabilita con successo!", "components.Settings.RadarrModal.toastRadarrTestFailure": "Impossibile connettersi a Radarr.", @@ -206,7 +196,6 @@ "components.TvDetails.network": "{networkCount, plural, one {Rete} other {Reti}}", "components.Setup.finishing": "Finalizzazione…", "components.Settings.menuJobs": "Processi & Cache", - "components.TvDetails.manageModalClearMediaWarning": "* Questo rimuoverà irreversibilmente tutti i dati per questa serie, incluse eventuali richieste. Se questo elemento esiste nella tua libreria Plex, le informazioni dei media saranno ricreate durante la prossima scansione.", "components.Setup.signinMessage": "Comincia accedendo con il tuo account Plex", "components.Settings.sonarrsettings": "Impostazioni Sonarr", "components.Settings.plexsettingsDescription": "Configura le impostazioni del tuo server Plex. Overseerr scansiona Plex a intervalli regolari alla ricerca di nuovi contenuti.", @@ -228,9 +217,9 @@ "components.Settings.SettingsAbout.helppaycoffee": "Offri un caffè", "components.Settings.SettingsAbout.Releases.viewongithub": "Visualizza su GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Visualizza il registro modifiche", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Registro versione", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Registro cambiamenti", "components.Settings.SettingsAbout.Releases.releases": "Versioni", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Dati di versione non disponibili. GitHub è down?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Dati di versione non sono momentaneamente disponibili.", "components.Settings.SettingsAbout.Releases.latestversion": "Versione più recente", "components.Settings.SettingsAbout.Releases.currentversion": "Versione attuale", "components.UserList.importfromplexerror": "Qualcosa è andato storto nell'importare gli utenti da Plex.", @@ -243,9 +232,7 @@ "components.MovieDetails.viewfullcrew": "Vedi troupe completa", "components.Settings.Notifications.allowselfsigned": "Consenti i certificati autofirmati", "components.TvDetails.firstAirDate": "Prima data di messa in onda", - "components.CollectionDetails.requestswillbecreated": "Per i titoli seguenti verranno create richieste:", "components.CollectionDetails.requestcollection": "Richiedi raccolta", - "components.CollectionDetails.requestSuccess": "{title} richiesto con successo!", "components.CollectionDetails.overview": "Trama", "components.CollectionDetails.numberofmovies": "{count} Film", "components.TvDetails.watchtrailer": "Guarda il trailer", @@ -286,7 +273,7 @@ "components.Settings.Notifications.NotificationsPushover.agentenabled": "Abilita Agente", "components.Settings.Notifications.NotificationsPushover.accessToken": "Token API applicazione", "components.RequestList.sortModified": "Ultima modifica", - "components.RequestList.sortAdded": "Data della richiesta", + "components.RequestList.sortAdded": "Più recente", "components.RequestList.showallrequests": "Mostra tutte le richieste", "components.RequestButton.declinerequest4k": "Rifiuta la richiesta 4K", "components.RequestButton.declinerequest": "Rifiuta la richiesta", @@ -369,11 +356,8 @@ "components.Login.signin": "Accedi", "components.MovieDetails.markavailable": "Segna come disponibile", "components.MovieDetails.mark4kavailable": "Segna come disponibile in 4K", - "components.MovieDetails.downloadstatus": "Stato dello scaricamento", "components.MovieDetails.playonplex": "Riproduci su Plex", "components.MovieDetails.play4konplex": "Riproduci in 4K su Plex", - "components.MovieDetails.openradarr4k": "Apri Film in 4K su Radarr", - "components.MovieDetails.openradarr": "Apri Film in Radarr", "components.Login.forgotpassword": "Password dimenticata?", "components.Discover.discover": "Esplora", "components.AppDataWarning.dockerVolumeMissingDescription": "Il volume {appDataPath} non è configurato correttamente. Tutte le modifiche apportate saranno perse quando il container verrà interrotto o riavviato.", @@ -453,29 +437,24 @@ "components.PlexLoginButton.signingin": "Accesso in corso…", "components.PermissionEdit.viewrequestsDescription": "Concede il permesso di visualizzare le richieste di altri utenti.", "components.PermissionEdit.viewrequests": "Visualizza le richieste", - "components.PermissionEdit.usersDescription": "Concede il permesso di gestire gli utenti Overseerr. Gli utenti con questo permesso non possono modificare gli utenti con privilegio di Amministratore, o concederlo.", + "components.PermissionEdit.usersDescription": "Concede il permesso di gestire gli utenti. Gli utenti con questo permesso non possono modificare gli utenti con privilegio di Amministratore, o concederlo.", "components.PermissionEdit.requestDescription": "Concedere l'autorizzazione per richiedere media non 4K.", - "components.PermissionEdit.settingsDescription": "Permette di modificare le impostazioni di Overseerr. Un utente deve avere questa autorizzazione per poterla concedere ad altri.", + "components.PermissionEdit.settingsDescription": "Permette di modificare le impostazioni globali. Un utente deve avere questa autorizzazione per poterla concedere ad altri.", "components.PermissionEdit.request4kTvDescription": "Concede l'autorizzazione per richiedere serie in 4K.", "components.PermissionEdit.request4kMoviesDescription": "Concede l'autorizzazione per richiedere film in 4K.", "components.PermissionEdit.request4kDescription": "Concede l'autorizzazione per richiedere media in 4K.", "components.PermissionEdit.request4k": "Richiesta 4K", "components.PermissionEdit.request": "Richiesta", - "components.PermissionEdit.managerequestsDescription": "Concede il permesso di gestire le richieste su Overseerr. Tutte le richieste fatte da un utente con questo permesso sono automaticamente approvate.", + "components.PermissionEdit.managerequestsDescription": "Concede il permesso di gestire le richieste. Tutte le richieste fatte da un utente con questo permesso sono automaticamente approvate.", "components.PermissionEdit.autoapproveSeriesDescription": "Concede l'approvazione automatica per le richieste di serie non in 4K.", "components.PermissionEdit.autoapproveMoviesDescription": "Concede l'approvazione automatica per le richieste di film non in 4K.", "components.PermissionEdit.autoapproveDescription": "Concede l'approvazione automatica per tutte le richieste non in 4K.", - "components.PermissionEdit.advancedrequestDescription": "Concede il permesso di usare opzioni di richiesta avanzate.", + "components.PermissionEdit.advancedrequestDescription": "Concede il permesso di modificare opzioni di richiesta avanzate.", "i18n.advanced": "Avanzato", "components.UserList.validationEmail": "Devi fornire un indirizzo e-mail valido", "components.UserList.users": "Utenti", "components.TvDetails.playonplex": "Riproduci su Plex", "components.TvDetails.play4konplex": "Riproduci in 4K su Plex", - "components.TvDetails.opensonarr4k": "Apri la serie in 4K Sonarr", - "components.TvDetails.opensonarr": "Apri la serie in Sonarr", - "components.TvDetails.markavailable": "Segna come disponibile", - "components.TvDetails.downloadstatus": "Stato dello scaricamento", - "components.TvDetails.allseasonsmarkedavailable": "* Tutte le stagioni saranno contrassegnate come disponibili.", "components.Setup.setup": "Impostazione", "components.Settings.validationApplicationUrlTrailingSlash": "L'URL non deve finire con una barra finale", "components.Settings.validationApplicationUrl": "Devi fornire un URL valido", @@ -490,15 +469,13 @@ "components.Settings.serverpresetRefreshing": "Recupero di server…", "components.Settings.serverpresetManualMessage": "Configurazione manuale", "components.TvDetails.nextAirDate": "Prossima data di messa in onda", - "components.TvDetails.mark4kavailable": "Segna come disponibile in 4K", "components.Settings.trustProxyTip": "Permette a Overseerr di registrare correttamente gli indirizzi IP dei client dietro un proxy (Overseerr deve essere ricaricato perché le modifiche abbiano effetto)", "components.Settings.settingUpPlexDescription": "Per impostare Plex, potete inserire i dati manualmente o selezionare un server recuperato da plex.tv. Premi il pulsante a destra del menu a tendina per recuperare la lista di server disponibili.", "components.Settings.Notifications.sendSilentlyTip": "Invia notifiche senza suono", "components.Settings.Notifications.sendSilently": "Invia silenziosamente", - "components.UserList.sortCreated": "Data di creazione", + "components.UserList.sortCreated": "Data di unione", "components.UserList.sortRequests": "Conteggio richieste", "components.UserList.sortDisplayName": "Nome da mostrare", - "components.UserList.sortUpdated": "Ultimo aggiornamento", "components.PermissionEdit.autoapprove4k": "Auto-approva 4K", "components.PermissionEdit.autoapprove4kMoviesDescription": "Concede l'approvazione automatica per le richieste di film in 4K.", "components.PermissionEdit.autoapprove4kMovies": "Auto-approva i film in 4K", @@ -545,7 +522,6 @@ "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Password attuale", "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Conferma la password", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "È necessario fornire un ID utente valido", - "components.CollectionDetails.requestswillbecreated4k": "Per i titoli seguenti verranno create richieste per 4K:", "components.CollectionDetails.requestcollection4k": "Richiedi Raccolta in 4K", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Regione da scoprire", "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Lingua da scoprire", @@ -622,7 +598,6 @@ "components.TvDetails.episodeRuntimeMinutes": "{runtime} minuti", "components.TvDetails.episodeRuntime": "Durata di un episodio", "components.Settings.partialRequestsEnabled": "Consente richieste di serie parziali", - "components.RequestModal.requestall": "Richiedi tutte le stagioni", "components.RequestModal.alreadyrequested": "Già richiesto", "components.Discover.TvGenreList.seriesgenres": "Generi serie", "components.Discover.MovieGenreList.moviegenres": "Generi film", @@ -795,7 +770,7 @@ "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "È necessario fornire un URL valido", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "Impostazioni LunaSea salvate con successo!", "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "Impossibile salvare le impostazioni di notifica LunaSea.", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Richiesto solo se non si usa il profilo predefinito", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Richiesto solo se non si usa il profilo default", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Nome Profilo", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Abilita Agente", "components.PermissionEdit.requestTvDescription": "Concedere l'autorizzazione per richiedere serie non 4K.", @@ -881,5 +856,144 @@ "components.MovieDetails.showless": "Mostra meno", "components.TvDetails.streamingproviders": "Ora in streaming su", "components.MovieDetails.streamingproviders": "Ora in streaming su", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.edit": "Modifica commento", + "components.IssueDetails.IssueComment.postedby": "Inviato {relativeTime} da{username}", + "components.IssueDetails.IssueComment.validationComment": "Devi inserire un messaggio", + "components.IssueDetails.IssueDescription.deleteissue": "Elimina segnalazione", + "components.IssueDetails.comments": "Commenti", + "components.IssueDetails.toasteditdescriptionfailed": "Qualcosa è andato storto durante la modifica della descrizione.", + "components.IssueDetails.closeissue": "Chiudi segnalazione", + "components.IssueDetails.closeissueandcomment": "Chiudi con un commento", + "components.IssueDetails.deleteissue": "Elimina segnalazione", + "components.IssueDetails.deleteissueconfirm": "Sei sicuro di voler eliminare questa segnalazione?", + "components.IssueDetails.issuepagetitle": "Segnalazione", + "components.IssueDetails.issuetype": "Tipo", + "components.IssueDetails.problemepisode": "Episodio coinvolto", + "components.IssueDetails.problemseason": "Stagione coinvolta", + "components.IssueDetails.unknownissuetype": "Sconosciuto", + "components.IssueDetails.toastissuedeleted": "Segnalazione eliminata con successo!", + "components.IssueDetails.toastissuedeletefailed": "Qualcosa è andato storto durante l'eliminazione della segnalazione.", + "components.IssueDetails.toaststatusupdated": "Stato segnalazione aggiornato correttamente!", + "components.IssueDetails.toaststatusupdatefailed": "Qualcosa è andato storto durante l'aggiornamento dello stato della segnalazione.", + "components.IssueList.IssueItem.problemepisode": "Episodio coinvolto", + "components.IssueModal.CreateIssueModal.issomethingwrong": "C'è un problema con {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Episodio coinvolto", + "components.IssueModal.CreateIssueModal.problemseason": "Stagione coinvolta", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Qualcosa è andato storto durante l'invio della segnalazione.", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Segnalazione per {title} inviata correttamente!", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Devi fornire una descrizione", + "components.IssueModal.CreateIssueModal.whatswrong": "Qual è il problema?", + "components.IssueModal.issueAudio": "Audio", + "components.IssueModal.issueOther": "Altro", + "components.IssueModal.issueSubtitles": "Sottotitolo", + "components.IssueModal.issueVideo": "Video", + "components.ManageSlideOver.markavailable": "Segna come disponibile", + "components.ManageSlideOver.tvshow": "serie tv", + "components.IssueDetails.IssueDescription.description": "Descrizione", + "components.IssueList.IssueItem.viewissue": "Visualizza segnalazione", + "components.IssueDetails.IssueComment.areyousuredelete": "Sei sicuro di voler eliminare questo commento?", + "components.IssueDetails.IssueComment.delete": "Elimina commento", + "components.IssueDetails.IssueComment.postedbyedited": "Inviato {relativeTime} da {username} (Modificato)", + "components.IssueDetails.leavecomment": "Commento", + "components.IssueDetails.IssueDescription.edit": "Modifica descrizione", + "components.IssueDetails.allepisodes": "Tutti gli episodi", + "components.IssueDetails.allseasons": "Tutte le stagioni", + "components.IssueDetails.episode": "Episodio {episodeNumber}", + "components.IssueDetails.lastupdated": "Ultimo aggiornamento", + "components.IssueDetails.nocomments": "Nessun commento.", + "components.IssueList.IssueItem.issuestatus": "Stato", + "components.IssueDetails.season": "Stagione {seasonNumber}", + "components.IssueList.IssueItem.issuetype": "Tipo", + "components.IssueList.IssueItem.unknownissuetype": "Sconosciuto", + "components.IssueList.sortModified": "Ultima modifica", + "components.IssueModal.CreateIssueModal.episode": "Episodio {episodeNumber}", + "components.IssueModal.CreateIssueModal.allepisodes": "Tutti gli episodi", + "components.IssueModal.CreateIssueModal.allseasons": "Tutte le stagioni", + "components.IssueList.issues": "Segnalazioni", + "components.IssueList.sortAdded": "Più recente", + "components.IssueDetails.toasteditdescriptionsuccess": "Descrizione della segnalazione modificata con successo!", + "components.IssueModal.CreateIssueModal.providedetail": "Fornisci una descrizione dettagliata del problema riscontrato.", + "components.IssueList.showallissues": "Mostra tutte le segnalazioni", + "components.IssueDetails.reopenissue": "Riapri segnalazione", + "components.IssueDetails.reopenissueandcomment": "Riapri con un commento", + "components.IssueModal.CreateIssueModal.reportissue": "Segnala un problema", + "components.IssueModal.CreateIssueModal.season": "Stagione {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Invia segnalazione", + "components.IssueModal.CreateIssueModal.toastviewissue": "Visualizza segnalazione", + "components.Layout.Sidebar.issues": "Segnalazioni", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Tutte le stagioni verranno segnalate come disponibili.", + "components.ManageSlideOver.manageModalClearMedia": "Pulisci dati media", + "components.ManageSlideOver.downloadstatus": "Stato download", + "components.ManageSlideOver.manageModalNoRequests": "Nessuna richiesta.", + "components.ManageSlideOver.manageModalRequests": "Richieste", + "components.ManageSlideOver.movie": "film", + "components.ManageSlideOver.manageModalTitle": "Gestisci {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Segna come disponibile in 4K", + "components.IssueDetails.openinarr": "Apri in {arr}", + "components.IssueDetails.playonplex": "Guarda su Plex", + "components.ManageSlideOver.openarr": "Apri in {arr}", + "components.ManageSlideOver.openarr4k": "Apri in 4K {arr}", + "components.IssueDetails.play4konplex": "Guarda in 4K su Plex", + "components.IssueDetails.openin4karr": "Apri in 4k {arr}", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episodio} other {Episodi}}", + "components.PermissionEdit.createissuesDescription": "Concedi il permesso di segnalare problemi con i contenuti.", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Stagione} other {Stagioni}}", + "components.ManageSlideOver.manageModalIssues": "Segnalazioni aperte", + "components.IssueModal.CreateIssueModal.extras": "Extra", + "components.PermissionEdit.manageissues": "Gestisci segnalazioni", + "components.NotificationTypeSelector.adminissuecommentDescription": "Ricevi una notifica quando altri utenti commentano nella segnalazione.", + "components.NotificationTypeSelector.issuecomment": "Commenti segnalazione", + "components.NotificationTypeSelector.issuecommentDescription": "Invia notifiche quando le segnalazioni ricevono nuovi commenti.", + "components.IssueDetails.openedby": "#{issueId} aperto {relativeTime} da {username}", + "components.IssueList.IssueItem.openeduserdate": "{date} da {user}", + "components.PermissionEdit.viewissues": "Guarda segnalazione", + "components.PermissionEdit.manageissuesDescription": "Concedi permesso per gestire le segnalazioni del contenuto.", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Qualcosa è andato storto nel salvataggio del job.", + "components.NotificationTypeSelector.userissuecommentDescription": "Ricevi una notifica quando una segnalazione riceve nuovi commenti.", + "components.Settings.SettingsJobsCache.editJobSchedule": "Modifica Job", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Ogni {jobScheduleHours, plural, one {ora} other {{jobScheduleHours} ore}}", + "components.IssueList.IssueItem.opened": "Aperto", + "components.PermissionEdit.createissues": "Segnala problemi", + "components.NotificationTypeSelector.userissuecreatedDescription": "Ottieni una notifica quando altri utenti segnalano dei problemi.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Ricevi una notifica quando le tue segnalazioni vengono risolte.", + "components.PermissionEdit.viewissuesDescription": "Concedi l'autorizzazione per visualizzare le segnalazioni sui media effettuati da altri utenti.", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frequenza", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Ogni {jobScheduleMinutes, plural, one {minuto} other {{jobScheduleMinutes} minuti}}", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Job modificato correttamente!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Impossibile salvare le impostazioni Pushover.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Impostazioni Pushover salvate con successo!", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Questo rimuoverà irreversibilmente tutti i dati per questo {mediaType}, incluse eventuali richieste. Se questo elemento esiste nella tua libreria Plex, le informazioni multimediali verranno ricreate durante la scansione successiva.", + "components.NotificationTypeSelector.issuecreated": "Problema Segnalato", + "components.NotificationTypeSelector.issuecreatedDescription": "Invia una notifica quando un problema viene segnalato.", + "components.NotificationTypeSelector.issueresolved": "Problema risolto", + "components.NotificationTypeSelector.issueresolvedDescription": "Invia una notifica quando un problema viene risolto.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Access Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "API Token applicazione", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Chiave utente o di un gruppo", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Crea un token dalle tue Impostazioni Account", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Impostazioni di Pushbullet salvate con successo!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Impossibile salvare le impostazioni di Pushbullet.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registra un applicazione per usarla con {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "La tua stringa di 30 caratteri per identificare utente o un gruppo", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Devi fornire un access token", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Devi fornire un application token valido", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Devi fornire una chiave utente o di gruppo valida", + "i18n.resolved": "Risolto", + "i18n.open": "Aperto", + "components.Settings.SettingsAbout.runningDevelop": "Stai eseguendo la versione develop di Overseerr, che è raccomandata soltanto a chi aiuta nello sviluppo o nel testing dell'applicazione.", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Ricevi una notifica quanto le segnalazioni vengono riaperte da altri utenti.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Ricevi una notifica quanto le segnalazioni vengono risolte da altri utenti.", + "components.NotificationTypeSelector.issuereopened": "Segnalazione Riaperta", + "components.NotificationTypeSelector.issuereopenedDescription": "Invia una notifica quando le segnalazioni vengono riaperte.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Ricevi una notifica quanto le tue segnalazioni vengono riaperte.", + "components.RequestModal.requestseasons4k": "Richiedi {seasonCount} {seasonCount, plural, one {Stagione} other {Stagioni}} in 4K", + "components.RequestModal.selectmovies": "Film selezionati", + "components.MovieDetails.productioncountries": "{countryCount, plural, one {Paese} other {Paesi}} di produzione", + "components.TvDetails.productioncountries": "{countryCount, plural, one {Paese} other {Paesi}} di produzione", + "components.RequestModal.requestmovies": "Richiedi {count} {count, plural, one {Film} other {Films}}", + "components.RequestModal.requestmovies4k": "Richiedi {count} {count, plural, one {Film} other {Films}} in 4K", + "components.IssueDetails.commentplaceholder": "Aggiungi un commento…", + "components.RequestModal.requestApproved": "Richiesta di {title} approvata!", + "components.RequestModal.approve": "Approva richiesta" } diff --git a/src/i18n/locale/ja.json b/src/i18n/locale/ja.json index fd4713dec..31097b50d 100644 --- a/src/i18n/locale/ja.json +++ b/src/i18n/locale/ja.json @@ -16,11 +16,6 @@ "components.Layout.UserDropdown.signout": "ログアウト", "components.MovieDetails.budget": "予算", "components.MovieDetails.cast": "出演者", - "components.MovieDetails.manageModalClearMedia": "メディアのデータを消去", - "components.MovieDetails.manageModalClearMediaWarning": "*リクエストを含め、すべての詳細情報が消去されます。この操作は元に戻すことができません。この作品が Plex ライブラリに存在する場合、詳細情報は次のスキャンで再作成されます。", - "components.MovieDetails.manageModalNoRequests": "リクエストがありません。", - "components.MovieDetails.manageModalRequests": "リクエスト", - "components.MovieDetails.manageModalTitle": "映画を管理", "components.MovieDetails.originallanguage": "オリジナルの言語", "components.MovieDetails.overview": "ストーリー", "components.MovieDetails.overviewunavailable": "ストーリー情報がありません。", @@ -148,11 +143,6 @@ "components.Setup.signinMessage": "Plex アカウントでログインして始める", "components.Setup.welcome": "Overseerr へようこそ", "components.TvDetails.cast": "出演者", - "components.TvDetails.manageModalClearMedia": "メディアのデータを消去", - "components.TvDetails.manageModalClearMediaWarning": "*リクエストを含め、メーディアのデータをすべて消去されます。この操作は元に戻すことができません。このアイテムが Plex ライブラリに存在する場合、メディア情報は次の同期で再作成されます。", - "components.TvDetails.manageModalNoRequests": "リクエストがありません。", - "components.TvDetails.manageModalRequests": "リクエスト", - "components.TvDetails.manageModalTitle": "シリーズの管理", "components.TvDetails.originallanguage": "オリジナルの言語", "components.TvDetails.overview": "ストーリー", "components.TvDetails.overviewunavailable": "ストーリー情報がありません。", @@ -160,7 +150,6 @@ "components.TvDetails.similar": "類似シリーズ", "components.UserList.admin": "管理者", "components.UserList.created": "作成日", - "components.UserList.lastupdated": "更新日", "components.UserList.plexuser": "Plexユーザー", "components.UserList.role": "役割", "components.UserList.totalrequests": "リクエスト数", @@ -235,9 +224,7 @@ "components.Settings.SettingsAbout.Releases.currentversion": "現在のバージョン", "components.MovieDetails.MovieCrew.fullcrew": "フルクルー", "components.MovieDetails.viewfullcrew": "フルクルーを表示", - "components.CollectionDetails.requestswillbecreated": "以下のタイトルをリクエストします:", "components.CollectionDetails.requestcollection": "リクエストコレクション", - "components.CollectionDetails.requestSuccess": "{title} をリクエストしました。", "components.CollectionDetails.overview": "ストーリー", "components.CollectionDetails.numberofmovies": "{count} 本の映画", "i18n.requested": "リクエスト済み", @@ -264,7 +251,6 @@ "components.Layout.UserDropdown.settings": "設定", "components.Layout.UserDropdown.myprofile": "プロフィール", "components.Discover.discover": "ホーム", - "components.CollectionDetails.requestswillbecreated4k": "以下のタイトルの 4K のリクエストをします:", "components.CollectionDetails.requestcollection4k": "4K のコレクションをリクエスト", "components.AppDataWarning.dockerVolumeMissingDescription": "{appDataPath} ボリュームマウントが正しく構成されていませんでした。コンテナーが停止または再起動されると、すべてのデータが消去されます。", "components.PlexLoginButton.signingin": "ログイン中…", @@ -275,7 +261,6 @@ "components.Login.signinwithplex": "Plex アカウントを使用する", "components.Login.signinwithoverseerr": "{applicationTitle} アカウントを使用する", "components.Login.signinheader": "続けるにはログインしてください", - "components.MovieDetails.downloadstatus": "ダウンロード状況", "components.MediaSlider.ShowMoreCard.seemore": "もっと見る", "components.Login.validationpasswordrequired": "パスワードを入力してください", "components.ResetPassword.validationemailrequired": "有効なメールアドレスを入力してください", @@ -303,7 +288,6 @@ "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "一般設定", "components.UserProfile.UserSettings.UserGeneralSettings.general": "一般", "components.Settings.general": "一般", - "components.TvDetails.downloadstatus": "ダウンロード状況", "pages.internalservererror": "内部サーバーエラー", "pages.somethingwentwrong": "問題が発生しました", "pages.serviceunavailable": "サービスが利用できません", @@ -443,25 +427,23 @@ "components.QuotaSelector.movieRequests": "{quotaDays} {days}に {quotaLimit} {movies}", "components.QuotaSelector.days": "日", "components.PermissionEdit.viewrequests": "リクエストを見る", - "components.PermissionEdit.usersDescription": "Overseerrユーザーを管理する権限を付与する。この権限を持つユーザーは、Admin 権限を持つユーザーの変更や、Admin 権限を付与することはできません。", + "components.PermissionEdit.usersDescription": "ユーザーを管理する権限を付与する。この権限を持つユーザーは、Admin 権限を持つユーザーの変更や、Admin 権限を付与することはできません。", "components.NotificationTypeSelector.mediarequestedDescription": "ユーザーが承認を必要とする新メディアリクエストをすると通知する。", "components.PermissionEdit.users": "ユーザー管理", - "components.PermissionEdit.settingsDescription": "Overseerrの設定を変更する権限を付与する。他の人にこの権限を付与するには、ユーザーがこの権限を持っていなければなりません。", + "components.PermissionEdit.settingsDescription": "設定を変更する権限を付与する。他の人にこの権限を付与するには、ユーザーがこの権限を持っていなければなりません。", "components.PermissionEdit.settings": "設定の管理", - "components.PermissionEdit.requestTvDescription": "4K以外のシリーズをリクエストする権限を与える。", + "components.PermissionEdit.requestTvDescription": "4K 以外のシリーズをリクエストする権限を与える。", "components.PermissionEdit.requestTv": "シリーズをリクエスト", - "components.PermissionEdit.requestMoviesDescription": "4K以外の映画をリクエストする権限を与える。", + "components.PermissionEdit.requestMoviesDescription": "4K 以外の映画をリクエストする権限を与える。", "components.PermissionEdit.requestMovies": "映画をリクエスト", - "components.PermissionEdit.requestDescription": "4K以外のメディアをリクエストする権限を与える。", + "components.PermissionEdit.requestDescription": "4K 以外のメディアをリクエストする権限を与える。", "components.PermissionEdit.request4kTvDescription": "4K シリーズをリクエストする権限を与える。", "components.PermissionEdit.request4kTv": "4K シリーズをリクエスト", - "components.PermissionEdit.request4kDescription": "4Kメディアをリクエストする権限を与える。", - "components.PermissionEdit.request4kMoviesDescription": "4K映画をリクエストする権限を与える。", + "components.PermissionEdit.request4kDescription": "4K メディアをリクエストする権限を与える。", + "components.PermissionEdit.request4kMoviesDescription": "4K 映画をリクエストする権限を与える。", "components.PermissionEdit.request4kMovies": "4K映画をリクエスト", - "components.PermissionEdit.managerequestsDescription": "Overseerrのリクエストを管理する権限を付与する。この権限を持つユーザーのリクエストは、すべて自動的に承認されます。", + "components.PermissionEdit.managerequestsDescription": "リクエストを管理する権限を付与する。この権限を持つユーザーのリクエストは、すべて自動的に承認されます。", "components.PermissionEdit.managerequests": "リクエストを管理", - "components.MovieDetails.openradarr4k": "4K Radarrで映画を開く", - "components.MovieDetails.openradarr": "Radarrで映画を開く", "components.MovieDetails.markavailable": "視聴可能をマーク", "components.MovieDetails.mark4kavailable": "4K視聴可能をマーク", "components.Layout.VersionStatus.streamstable": "Overseerr安定版", @@ -521,5 +503,17 @@ "components.Settings.SonarrModal.validationLanguageProfileRequired": "言語プロフィールを選択してください", "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "表示言語", "components.Settings.locale": "表示言語", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.UserProfile.norequests": "リクエストが有りません。", + "components.ManageSlideOver.allseasonsmarkedavailable": "", + "components.ManageSlideOver.downloadstatus": "ダウンロード状況", + "components.ManageSlideOver.manageModalClearMedia": "メディアのデータを消去", + "components.ManageSlideOver.manageModalRequests": "リクエスト", + "components.ManageSlideOver.openarr": "{arr} を開く", + "components.ManageSlideOver.manageModalClearMediaWarning": "※リクエストを含め、すべての詳細情報が消去されます。この操作は元に戻すことができません。この作品が Plex ライブラリに存在する場合、詳細情報は次のスキャンで再作成されます。", + "components.ManageSlideOver.openarr4k": "4K {arr} を開く", + "components.ManageSlideOver.manageModalNoRequests": "リクエストが有りません。", + "components.ManageSlideOver.manageModalTitle": "{mediaType}を管理", + "components.ManageSlideOver.movie": "映画", + "components.ManageSlideOver.tvshow": "シリーズ" } diff --git a/src/i18n/locale/nb_NO.json b/src/i18n/locale/nb_NO.json index 43195696a..634e26877 100644 --- a/src/i18n/locale/nb_NO.json +++ b/src/i18n/locale/nb_NO.json @@ -16,11 +16,6 @@ "components.Layout.UserDropdown.signout": "Logg ut", "components.MovieDetails.budget": "Budsjett", "components.MovieDetails.cast": "Roller", - "components.MovieDetails.manageModalClearMedia": "Tøm mediedata", - "components.MovieDetails.manageModalClearMediaWarning": "Dette vil fjerne all mediedata, inkludert alle forespørsler for denne filmen, for godt. Hvis elementet finnes i ditt Plex-bibliotek, vil mediainfoen gjenskapes ved neste synkronisering.", - "components.MovieDetails.manageModalNoRequests": "Ingen forespørsler", - "components.MovieDetails.manageModalRequests": "Forespørsler", - "components.MovieDetails.manageModalTitle": "Administrer film", "components.MovieDetails.originallanguage": "Originalspråk", "components.MovieDetails.overview": "Oversikt", "components.MovieDetails.overviewunavailable": "Oversikt utilgjengelig.", @@ -148,11 +143,6 @@ "components.Setup.signinMessage": "Start med å logge inn på din Plex-konto", "components.Setup.welcome": "Velkommen til Overseerr", "components.TvDetails.cast": "Roller", - "components.TvDetails.manageModalClearMedia": "Tøm all mediedata", - "components.TvDetails.manageModalClearMediaWarning": "Dette vil fjerne all mediedata, inkludert alle forespørsler om dette elementet, for godt. Hvis dette elementet finnes i ditt Plex-bibliotek, vil medieinfoen bli gjenskapt ved neste synkronisering.", - "components.TvDetails.manageModalNoRequests": "Ingen forespørsler", - "components.TvDetails.manageModalRequests": "Forespørsler", - "components.TvDetails.manageModalTitle": "Håndter serier", "components.TvDetails.originallanguage": "Originalspråk", "components.TvDetails.overview": "Oversikt", "components.TvDetails.overviewunavailable": "Oversikt utilgjengelig", @@ -160,7 +150,6 @@ "components.TvDetails.similar": "Lignende serier", "components.UserList.admin": "Administrator", "components.UserList.created": "Opprettet", - "components.UserList.lastupdated": "Oppdatert", "components.UserList.plexuser": "Plex-bruker", "components.UserList.role": "Rolle", "components.UserList.totalrequests": "Forespørsler", @@ -234,11 +223,8 @@ "components.Discover.DiscoverNetwork.networkSeries": "{network} Serier", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Filmer", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Filmer", - "components.CollectionDetails.requestswillbecreated4k": "Det vil opprettes 4K forespørsler for følgende titler:", - "components.CollectionDetails.requestswillbecreated": "Det vil opprettes forespørsler for følgende titler:", "components.CollectionDetails.requestcollection4k": "Forespør samling i 4K", "components.CollectionDetails.requestcollection": "Forespør samling", - "components.CollectionDetails.requestSuccess": "{title} ble forespurt!", "components.CollectionDetails.overview": "Oversikt", "components.CollectionDetails.numberofmovies": "{count} Filmer", "components.UserProfile.ProfileHeader.profile": "Vis profil", @@ -269,7 +255,6 @@ "components.ResetPassword.confirmpassword": "Verifiser passord", "components.RequestModal.requesterror": "Noe gikk galt ved sending av forespørsel.", "components.RequestModal.requestcancelled": "Forespørsel for {title} kansellert.", - "components.RequestModal.requestall": "Forespør alle sesonger", "components.RequestModal.request4ktitle": "Forespør {title} i 4K", "components.RequestModal.pending4krequest": "Ventende 4K forespørsel for {title}", "components.RequestModal.errorediting": "Noe gikk galt under endring av forespørselen.", @@ -352,11 +337,8 @@ "components.MovieDetails.watchtrailer": "Se Trailer", "components.MovieDetails.playonplex": "Spill av i Plex", "components.MovieDetails.play4konplex": "Spill av i 4K i Plex", - "components.MovieDetails.openradarr4k": "Åpne film i 4K Radarr", - "components.MovieDetails.openradarr": "Åpne film i Radarr", "components.MovieDetails.markavailable": "Merk som Tilgjengelig", "components.MovieDetails.mark4kavailable": "Merk som tilgjengelig i 4K", - "components.MovieDetails.downloadstatus": "Nedlastningsstatus", "components.MediaSlider.ShowMoreCard.seemore": "Vis mer", "components.Login.validationpasswordrequired": "Du må skrive et passord", "components.Login.validationemailrequired": "Du må bruke en gyldig e-postadresse", @@ -385,15 +367,12 @@ "components.UserProfile.seriesrequest": "Serieforespørsler", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Serieforespørselbegrensning", "components.TvDetails.showtype": "Serietype", - "components.TvDetails.opensonarr4k": "Vis serie i 4K-Sonarr", - "components.TvDetails.opensonarr": "Vis serie i Sonarr", "i18n.notrequested": "Ikke forespurt", "components.QuotaSelector.unlimited": "Ubegrenset", "components.MovieDetails.originaltitle": "Original-tittel", "i18n.all": "Alle", "components.UserList.deleteconfirm": "Er du sikker på at du ønsker å slette denne brukeren? All forespørseldata for denne brukeren vil bli slettet.", "components.UserList.autogeneratepassword": "Generer passord automatisk", - "components.TvDetails.allseasonsmarkedavailable": "* Alle sesonger vil bli merket som tilgjengelige.", "components.Settings.originallanguageTip": "Filtrer innhold basert på originalspråk", "components.Settings.originallanguage": "Oppdag-språk", "components.Settings.SettingsLogs.showall": "Vis all logg", @@ -489,7 +468,6 @@ "components.UserList.owner": "Eier", "components.UserList.sortDisplayName": "Visningsnavn", "components.UserList.sortRequests": "Antall forespørsler", - "components.UserList.sortUpdated": "Sist endret", "components.UserList.sortCreated": "Opprettelsesdato", "components.UserList.createlocaluser": "Opprett lokal bruker", "components.UserList.accounttype": "Kontotype", diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json index 73b919442..ef32b2bbb 100644 --- a/src/i18n/locale/nl.json +++ b/src/i18n/locale/nl.json @@ -16,11 +16,6 @@ "components.Layout.UserDropdown.signout": "Uitloggen", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cast": "Cast", - "components.MovieDetails.manageModalClearMedia": "Wis mediadata", - "components.MovieDetails.manageModalClearMediaWarning": "* Dit wist alle mediadata voor dit item onherroepelijk, inclusief eventuele verzoeken. Als dit item in je Plex-bibliotheek staat, zal alle media-informatie bij de volgende scan hersteld worden.", - "components.MovieDetails.manageModalNoRequests": "Geen verzoeken.", - "components.MovieDetails.manageModalRequests": "Verzoeken", - "components.MovieDetails.manageModalTitle": "Film beheren", "components.MovieDetails.originallanguage": "Originele taal", "components.MovieDetails.overview": "Overzicht", "components.MovieDetails.overviewunavailable": "Overzicht niet beschikbaar.", @@ -78,7 +73,7 @@ "components.Settings.RadarrModal.toastRadarrTestFailure": "Kon niet verbinden met Radarr.", "components.Settings.RadarrModal.toastRadarrTestSuccess": "Succesvol verbonden met Radarr-server!", "components.Settings.RadarrModal.validationApiKeyRequired": "Je moet een API-sleutel opgeven", - "components.Settings.RadarrModal.validationHostnameRequired": "Je moet een hostnaam of IP-adres opgeven", + "components.Settings.RadarrModal.validationHostnameRequired": "Je moet een geldige hostnaam of geldig IP-adres opgeven", "components.Settings.RadarrModal.validationPortRequired": "Je moet een geldig poortnummer opgeven", "components.Settings.RadarrModal.validationProfileRequired": "Je moet een kwaliteitsprofiel selecteren", "components.Settings.RadarrModal.validationRootFolderRequired": "Je moet een hoofdmap selecteren", @@ -99,7 +94,7 @@ "components.Settings.SonarrModal.servername": "Servernaam", "components.Settings.SonarrModal.ssl": "SSL gebruiken", "components.Settings.SonarrModal.validationApiKeyRequired": "Je moet een API-sleutel opgeven", - "components.Settings.SonarrModal.validationHostnameRequired": "Je moet een hostnaam of IP-adres opgeven", + "components.Settings.SonarrModal.validationHostnameRequired": "Je moet een geldige hostnaam of geldig IP-adres opgeven", "components.Settings.SonarrModal.validationPortRequired": "Je moet een geldig poortnummer opgeven", "components.Settings.SonarrModal.validationProfileRequired": "Je moet een kwaliteitsprofiel selecteren", "components.Settings.SonarrModal.validationRootFolderRequired": "Je moet een hoofdmap selecteren", @@ -148,19 +143,13 @@ "components.Setup.signinMessage": "Ga aan de slag door in te loggen met je Plex-account", "components.Setup.welcome": "Welkom bij Overseerr", "components.TvDetails.cast": "Cast", - "components.TvDetails.manageModalClearMedia": "Wis media-data", - "components.TvDetails.manageModalClearMediaWarning": "* Dit wist alle mediadata voor deze serie onherroepelijk, inclusief eventuele verzoeken. Als dit item in je Plex-bibliotheek staat, zal alle media-informatie bij de volgende scan hersteld worden.", - "components.TvDetails.manageModalNoRequests": "Geen verzoeken.", - "components.TvDetails.manageModalRequests": "Verzoeken", - "components.TvDetails.manageModalTitle": "Serie beheren", "components.TvDetails.originallanguage": "Originele taal", "components.TvDetails.overview": "Overzicht", "components.TvDetails.overviewunavailable": "Overzicht niet beschikbaar.", "components.TvDetails.recommendations": "Aanbevelingen", "components.TvDetails.similar": "Vergelijkbare series", "components.UserList.admin": "Beheerder", - "components.UserList.created": "Aangemaakt", - "components.UserList.lastupdated": "Gewijzigd", + "components.UserList.created": "Lid geworden", "components.UserList.plexuser": "Plex-gebruiker", "components.UserList.role": "Rol", "components.UserList.totalrequests": "Verzoeken", @@ -185,7 +174,7 @@ "components.TvDetails.TvCast.fullseriescast": "Volledige cast van de serie", "components.Settings.Notifications.emailsettingssaved": "Instellingen voor e-mailmeldingen met succes opgeslagen!", "components.Settings.Notifications.emailsettingsfailed": "Instellingen voor e-mailmeldingen konden niet opgeslagen worden.", - "components.Settings.Notifications.discordsettingssaved": "Instellingen voor Discord-meldingen zijn met succes opgeslagen!", + "components.Settings.Notifications.discordsettingssaved": "Instellingen voor Discord-meldingen zijn succesvol opgeslagen!", "components.Settings.Notifications.discordsettingsfailed": "Instellingen voor Discord-meldingen konden niet opgeslagen worden.", "components.Settings.validationPortRequired": "Je moet een geldig poortnummer opgeven", "components.Settings.validationHostnameRequired": "Je moet een geldig(e) hostnaam of IP-adres opgeven", @@ -263,8 +252,6 @@ "components.PersonDetails.crewmember": "Crew", "components.NotificationTypeSelector.mediarequested": "Media aangevraagd", "components.NotificationTypeSelector.mediaavailable": "Media beschikbaar", - "components.CollectionDetails.requestswillbecreated": "De volgende titels zullen aangevraagd worden:", - "components.CollectionDetails.requestSuccess": "{title} succesvol aangevraagd!", "components.MovieDetails.watchtrailer": "Trailer bekijken", "components.MovieDetails.viewfullcrew": "Volledige crew bekijken", "components.MovieDetails.MovieCrew.fullcrew": "Volledige crew", @@ -272,17 +259,17 @@ "components.UserList.importedfromplex": "{userCount, plural, one {# nieuwe gebruiker} other {# nieuwe gebruikers}} succesvol geïmporteerd vanuit Plex!", "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} changelog", "components.Settings.SettingsAbout.Releases.releases": "Versies", - "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Instellingen voor Slack-meldingen met succes opgeslagen!", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Instellingen voor Slack-meldingen succesvol opgeslagen!", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Instellingen voor Slack-meldingen konden niet opgeslagen worden.", "components.NotificationTypeSelector.mediarequestedDescription": "Een melding sturen wanneer gebruikers een nieuw mediaverzoek indienen dat goedkeuring vereist.", "components.NotificationTypeSelector.mediafailedDescription": "Een melding sturen wanneer een mediaverzoek niet toegevoegd kan worden aan Radarr of Sonarr.", "components.TvDetails.TvCrew.fullseriescrew": "Volledige crew van de serie", - "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Instellingen voor Pushover-meldingen met succes opgeslagen!", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Instellingen voor Pushover-meldingen succesvol opgeslagen!", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Instellingen voor Pushover-meldingen konden niet opgeslagen worden.", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Agent inschakelen", "components.Settings.Notifications.NotificationsPushover.accessToken": "Token toepassings-API", "components.RequestList.sortModified": "Laatst gewijzigd", - "components.RequestList.sortAdded": "Aanvraagdatum", + "components.RequestList.sortAdded": "Meest recent", "components.RequestList.showallrequests": "Toon alle verzoeken", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Je moet een geldige gebruikers- of groepssleutel opgeven", "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Je moet een geldig toepassingstoken opgeven", @@ -294,12 +281,12 @@ "components.RequestButton.declinerequests": "{requestCount, plural, one {verzoek} other {{requestCount} verzoeken}} weigeren", "components.RequestButton.decline4krequests": "{requestCount, plural, one {4K-verzoek} other {{requestCount} 4K-verzoeken}} weigeren", "components.StatusBadge.status4k": "4K {status}", - "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Instellingen voor webhook-meldingen met succes opgeslagen!", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Instellingen voor webhook-meldingen succesvol opgeslagen!", "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Instellingen voor webhook-meldingen konden niet opgeslagen worden.", "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook-URL", "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "Je moet een geldige JSON-payload opgeven", "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Hulp met sjabloonvariabelen", - "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON-payload met succes teruggezet!", + "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON-payload succesvol teruggezet!", "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Terugzetten naar standaard", "components.Settings.Notifications.NotificationsWebhook.customJson": "JSON-payload", "components.Settings.Notifications.NotificationsWebhook.authheader": "Autorisatie-header", @@ -379,9 +366,9 @@ "components.Settings.serverLocal": "lokaal", "components.Settings.csrfProtectionTip": "Externe API-toegang instellen op alleen-lezen (vereist HTTPS en Overseerr moet opnieuw worden geladen om wijzigingen door te voeren)", "components.Settings.csrfProtection": "CSRF-bescherming inschakelen", - "components.PermissionEdit.usersDescription": "Toestemming geven om Overseerr-gebruikers te beheren. Gebruikers met deze toestemming kunnen gebruikers met beheerdersrechten niet wijzigen of die rechten verlenen.", + "components.PermissionEdit.usersDescription": "Toestemming geven om gebruikers te beheren. Gebruikers met deze toestemming kunnen gebruikers met beheerdersrechten niet wijzigen of die rechten verlenen.", "components.PermissionEdit.users": "Gebruikers beheren", - "components.PermissionEdit.settingsDescription": "Toestemming geven om Overseerr-instellingen te wijzigen. Een gebruiker heeft deze machtiging nodig om ze aan anderen te verlenen.", + "components.PermissionEdit.settingsDescription": "Toestemming geven om algemene instellingen te wijzigen. Een gebruiker heeft deze machtiging nodig om ze aan anderen te verlenen.", "components.PermissionEdit.settings": "Instellingen beheren", "components.PermissionEdit.requestDescription": "Toestemming geven om niet-4K-media aan te vragen.", "components.PermissionEdit.request4kTvDescription": "Toestemming geven om series in 4K aan te vragen.", @@ -391,35 +378,26 @@ "components.PermissionEdit.request": "Aanvragen", "components.PermissionEdit.request4kMovies": "4K-films aanvragen", "components.PermissionEdit.request4kDescription": "Toestemming geven om 4K-media aan te vragen.", - "components.PermissionEdit.managerequestsDescription": "Toestemming geven om verzoeken te beheren. Alle verzoeken die door een gebruiker met deze machtiging worden gedaan, worden automatisch goedgekeurd.", + "components.PermissionEdit.managerequestsDescription": "Toestemming geven om mediaverzoeken te beheren. Alle verzoeken die door een gebruiker met deze machtiging worden gedaan, worden automatisch goedgekeurd.", "components.PermissionEdit.managerequests": "Verzoeken beheren", "components.PermissionEdit.autoapproveSeriesDescription": "Serieverzoeken (niet 4K) automatisch goedkeuren.", "components.PermissionEdit.autoapproveMovies": "Films automatisch goedkeuren", "components.PermissionEdit.autoapproveSeries": "Series automatisch goedkeuren", "components.PermissionEdit.autoapproveMoviesDescription": "Filmverzoeken (niet 4K) automatisch goedkeuren.", - "components.PermissionEdit.autoapproveDescription": "Alle verzoeken (niet 4K) automatisch goedkeuren.", + "components.PermissionEdit.autoapproveDescription": "Alle mediaverzoeken (niet 4K) automatisch goedkeuren.", "components.PermissionEdit.autoapprove": "Automatische goedkeuring", - "components.PermissionEdit.advancedrequestDescription": "Toestemming geven om geavanceerde aanvraagopties te gebruiken.", + "components.PermissionEdit.advancedrequestDescription": "Toestemming geven om geavanceerde aanvraagopties voor media te wijzigen.", "components.PermissionEdit.adminDescription": "Volledige beheerderstoegang. Omzeilt alle andere machtigingscontroles.", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Succesvol verbonden met Sonarr!", "components.Settings.SonarrModal.toastSonarrTestFailure": "Kon niet verbinden met Sonarr.", "components.TvDetails.playonplex": "Afspelen op Plex", "components.TvDetails.play4konplex": "Afspelen in 4K op Plex", - "components.TvDetails.opensonarr4k": "Serie openen in 4K Sonarr", - "components.TvDetails.opensonarr": "Serie openen in Sonarr", - "components.TvDetails.downloadstatus": "Downloadstatus", "components.Settings.SonarrModal.syncEnabled": "Scan inschakelen", "components.Settings.SonarrModal.externalUrl": "Externe URL", "components.Settings.RadarrModal.syncEnabled": "Scan inschakelen", "components.Settings.RadarrModal.externalUrl": "Externe URL", "components.MovieDetails.playonplex": "Afspelen op Plex", "components.MovieDetails.play4konplex": "Afspelen in 4K op Plex", - "components.MovieDetails.openradarr4k": "Film openen in 4K Radarr", - "components.MovieDetails.openradarr": "Film openen in Radarr", - "components.MovieDetails.downloadstatus": "Downloadstatus", - "components.TvDetails.markavailable": "Als beschikbaar markeren", - "components.TvDetails.mark4kavailable": "Als beschikbaar in 4K markeren", - "components.TvDetails.allseasonsmarkedavailable": "* Alle seizoenen worden als beschikbaar gemarkeerd.", "components.MovieDetails.mark4kavailable": "Als beschikbaar in 4K markeren", "components.MovieDetails.markavailable": "Als beschikbaar markeren", "components.Settings.trustProxyTip": "Overseerr toestaan om IP-adressen van clients correct te registreren achter een proxy (Overseerr moet opnieuw worden geladen om de wijzigingen door te voeren)", @@ -462,7 +440,7 @@ "components.Settings.SonarrModal.validationApplicationUrl": "Je moet een geldige URL opgeven", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL mag niet eindigen op een schuine streep", "components.Settings.RadarrModal.validationApplicationUrl": "Je moet een geldige URL opgeven", - "components.PermissionEdit.viewrequestsDescription": "Toestemming geven om de verzoeken van andere gebruikers te bekijken.", + "components.PermissionEdit.viewrequestsDescription": "Toestemming geven om mediaverzoeken van andere gebruikers te bekijken.", "components.PermissionEdit.viewrequests": "Verzoeken bekijken", "components.UserList.validationEmail": "Je moet een geldig e-mailadres opgeven", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Basis-URL mag niet eindigen op een schuine streep", @@ -495,15 +473,14 @@ "components.Settings.SonarrModal.animelanguageprofile": "Taalprofiel anime", "components.Settings.Notifications.sendSilentlyTip": "Meldingen versturen zonder geluid", "components.Settings.Notifications.sendSilently": "Stil verzenden", - "components.UserList.sortUpdated": "Laatst gewijzigd", "components.UserList.sortRequests": "Aantal verzoeken", "components.UserList.sortDisplayName": "Weergavenaam", - "components.UserList.sortCreated": "Aanmaakdatum", + "components.UserList.sortCreated": "Aanmeldingsdatum", "components.PermissionEdit.autoapprove4kSeriesDescription": "Serieverzoeken in 4K automatisch goedkeuren.", "components.PermissionEdit.autoapprove4kSeries": "Series automatisch goedkeuren", "components.PermissionEdit.autoapprove4kMoviesDescription": "Filmverzoeken in 4K automatisch goedkeuren.", "components.PermissionEdit.autoapprove4kMovies": "Automatische goedkeuring van films in 4K", - "components.PermissionEdit.autoapprove4kDescription": "Alle 4K-verzoeken automatisch goedkeuren.", + "components.PermissionEdit.autoapprove4kDescription": "Alle 4K-mediaverzoeken automatisch goedkeuren.", "components.PermissionEdit.autoapprove4k": "Automatische goedkeuring 4K", "components.UserProfile.recentrequests": "Recente verzoeken", "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Algemene instellingen", @@ -545,7 +522,6 @@ "components.Layout.UserDropdown.myprofile": "Profiel", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Je moet een geldige gebruikers-ID opgeven", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "Het ID-nummer van je gebruikersaccount", - "components.CollectionDetails.requestswillbecreated4k": "De volgende titels zullen in 4K aangevraagd worden:", "components.CollectionDetails.requestcollection4k": "Collectie in 4K aanvragen", "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Inhoud filteren op regionale beschikbaarheid", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Regio van Ontdekken", @@ -570,7 +546,7 @@ "components.UserList.accounttype": "Type", "components.Settings.SettingsJobsCache.unknownJob": "Onbekende taak", "components.Settings.SettingsJobsCache.download-sync-reset": "Reset download sync", - "components.Settings.SettingsJobsCache.download-sync": "Synchroniseer downloads", + "components.Settings.SettingsJobsCache.download-sync": "Synchronisatie downloads", "components.TvDetails.seasons": "{seasonCount, plural, one {# seizoen} other {# seizoenen}}", "i18n.loading": "Bezig met laden…", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Je moet een geldige chat-ID opgeven", @@ -623,7 +599,6 @@ "components.Discover.TvGenreSlider.tvgenres": "Seriegenres", "components.Discover.MovieGenreSlider.moviegenres": "Filmgenres", "components.Settings.partialRequestsEnabled": "Gedeeltelijke serieverzoeken toestaan", - "components.RequestModal.requestall": "Alle seizoenen aanvragen", "components.RequestModal.alreadyrequested": "Al aangevraagd", "components.Discover.TvGenreList.seriesgenres": "Seriegenres", "components.Discover.MovieGenreList.moviegenres": "Filmgenres", @@ -744,11 +719,11 @@ "components.RequestCard.mediaerror": "De gekoppelde titel voor dit verzoek is niet meer beschikbaar.", "components.RequestCard.deleterequest": "Verzoek verwijderen", "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Je moet een geldige openbare PGP-sleutel opgeven", - "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Instellingen Telegrammeldingen met succes opgeslagen!", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Instellingen Telegrammeldingen succesvol opgeslagen!", "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "De instellingen voor Telegrammeldingen konden niet opgeslagen worden.", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "E-mailberichten versleutelen met OpenPGP", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "Openbare PGP-sleutel", - "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Instellingen voor e-mailmeldingen met succes opgeslagen!", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Instellingen voor e-mailmeldingen succesvol opgeslagen!", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Instellingen voor e-mailmeldingen konden niet opgeslagen worden.", "components.UserProfile.UserSettings.UserNotificationSettings.email": "E-mail", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Instellingen voor Discord-meldingen zijn met succes opgeslagen!", @@ -789,7 +764,7 @@ "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Instellingen voor web-pushmeldingen zijn niet opgeslagen.", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Profielnaam", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Agent inschakelen", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Instellingen voor web-pushmeldingen met succes opgeslagen!", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Instellingen voor web-pushmeldingen succesvol opgeslagen!", "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Instellingen voor web-pushmeldingen konden niet worden opgeslagen.", "components.Settings.noDefault4kServer": "Een 4K-{serverType}server moet als standaard worden gemarkeerd om gebruikers toe te laten om 4K-{mediaType} aan te vragen.", "components.Settings.is4k": "4K", @@ -888,5 +863,135 @@ "components.Settings.SettingsJobsCache.editJobSchedule": "Taak wijzigen", "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Elk(e) {jobScheduleHours, plural, one {uur} other {{jobScheduleHours} uren}}", "components.Settings.SettingsAbout.runningDevelop": "Je voert de developversie van Overseerr uit, die alleen wordt aanbevolen als je bijdraagt aan de ontwikkeling of de allereerste versies helpt testen.", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.areyousuredelete": "Weet je zeker dat je deze opmerking wilt verwijderen?", + "components.IssueDetails.IssueComment.delete": "Opmerking verwijderen", + "components.IssueDetails.IssueComment.edit": "Opmerking bewerken", + "components.IssueDetails.IssueComment.postedby": "{relativeTime} gepost door {username}", + "components.IssueDetails.IssueComment.postedbyedited": "{relativeTime} gepost door {username} (bewerkt)", + "components.IssueDetails.IssueComment.validationComment": "Je moet een bericht invoeren", + "components.IssueDetails.problemepisode": "Getroffen aflevering", + "components.IssueDetails.problemseason": "Getroffen seizoen", + "components.ManageSlideOver.mark4kavailable": "Als beschikbaar in 4K markeren", + "components.ManageSlideOver.markavailable": "Als beschikbaar markeren", + "components.ManageSlideOver.movie": "film", + "components.IssueDetails.comments": "Opmerkingen", + "components.ManageSlideOver.openarr": "Openen in {arr}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Gebruikers- of groepssleutel", + "i18n.open": "Onopgelost", + "i18n.resolved": "Opgelost", + "components.IssueModal.CreateIssueModal.season": "Seizoen {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Probleem indienen", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Je moet een beschrijving geven", + "components.IssueModal.CreateIssueModal.whatswrong": "Wat is er aan de hand?", + "components.IssueModal.issueSubtitles": "Ondertiteling", + "components.IssueModal.issueVideo": "Video", + "components.ManageSlideOver.downloadstatus": "Downloadstatus", + "components.ManageSlideOver.manageModalNoRequests": "Geen verzoeken.", + "components.IssueDetails.lastupdated": "Laatst bijgewerkt", + "components.IssueDetails.IssueDescription.deleteissue": "Probleem verwijderen", + "components.IssueDetails.IssueDescription.edit": "Beschrijving bewerken", + "components.IssueDetails.allseasons": "Alle seizoenen", + "components.IssueDetails.closeissue": "Probleem afsluiten", + "components.IssueModal.CreateIssueModal.allepisodes": "Alle afleveringen", + "components.IssueModal.issueAudio": "Audio", + "components.IssueDetails.nocomments": "Geen opmerkingen.", + "components.IssueModal.CreateIssueModal.reportissue": "Een probleem melden", + "components.IssueDetails.allepisodes": "Alle afleveringen", + "components.IssueDetails.toasteditdescriptionsuccess": "Probleembeschrijving succesvol bewerkt!", + "components.IssueDetails.toastissuedeleted": "Probleem succesvol verwijderd!", + "components.IssueModal.CreateIssueModal.providedetail": "Geef een gedetailleerde uitleg van het probleem dat je bent tegengekomen.", + "components.IssueDetails.toaststatusupdated": "Probleemstatus succesvol bijgewerkt!", + "components.IssueDetails.closeissueandcomment": "Afsluiten met opmerking", + "components.IssueModal.CreateIssueModal.problemseason": "Getroffen seizoen", + "components.IssueDetails.openedby": "#{issueId} {relativeTime} ingediend door {username}", + "components.IssueDetails.IssueDescription.description": "Beschrijving", + "components.NotificationTypeSelector.issuecommentDescription": "Melding sturen wanneer problemen nieuwe opmerkingen krijgen.", + "components.IssueModal.CreateIssueModal.toastviewissue": "Probleem bekijken", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Er ging iets mis bij het indienen van het probleem.", + "components.IssueDetails.toastissuedeletefailed": "Er ging iets mis bij het verwijderen van het probleem.", + "components.IssueDetails.toaststatusupdatefailed": "Er ging iets mis bij het updaten van de probleemstatus.", + "components.IssueDetails.deleteissue": "Probleem verwijderen", + "components.IssueDetails.episode": "Aflevering {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Probleem", + "components.IssueDetails.issuetype": "Type", + "components.IssueDetails.leavecomment": "Opmerking geven", + "components.IssueDetails.deleteissueconfirm": "Weet je zeker dat je dit probleem wilt verwijderen?", + "components.IssueDetails.unknownissuetype": "Onbekend", + "components.IssueDetails.openinarr": "Openen in {arr}", + "components.IssueDetails.toasteditdescriptionfailed": "Er ging iets mis bij het bewerken van de beschrijving van het probleem.", + "components.IssueList.IssueItem.issuetype": "Type", + "components.IssueList.IssueItem.opened": "Onopgelost", + "components.IssueDetails.reopenissue": "Probleem opnieuw indienen", + "components.IssueDetails.reopenissueandcomment": "Opnieuw indienen met opmerking", + "components.IssueDetails.season": "Seizoen {seasonNumber}", + "components.IssueList.showallissues": "Alle problemen weergeven", + "components.IssueList.sortModified": "Laatst gewijzigd", + "components.IssueModal.CreateIssueModal.allseasons": "Alle seizoenen", + "components.IssueModal.CreateIssueModal.episode": "Aflevering {episodeNumber}", + "components.IssueList.issues": "Problemen", + "components.IssueList.sortAdded": "Meest recent", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.openeduserdate": "{date} door {user}", + "components.IssueList.IssueItem.unknownissuetype": "Onbekend", + "components.IssueList.IssueItem.viewissue": "Probleem bekijken", + "components.IssueList.IssueItem.problemepisode": "Getroffen aflevering", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Is er een probleem met {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Getroffen aflevering", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Probleemmelding voor {title} succesvol ingediend!", + "components.PermissionEdit.viewissues": "Problemen weergeven", + "components.IssueModal.issueOther": "Andere", + "components.Layout.Sidebar.issues": "Problemen", + "components.ManageSlideOver.manageModalClearMedia": "Mediagegevens wissen", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Alle seizoenen zullen worden gemarkeerd als beschikbaar.", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Hiermee worden alle gegevens voor deze {mediaType} onomkeerbaar verwijderd, inclusief eventuele verzoeken. Als dit item in je Plex-bibliotheek staat, worden de mediagegevens opnieuw aangemaakt tijdens de volgende scan.", + "components.ManageSlideOver.manageModalRequests": "Verzoeken", + "components.ManageSlideOver.manageModalTitle": "{mediaType} beheren", + "components.ManageSlideOver.tvshow": "serie", + "components.NotificationTypeSelector.userissueresolvedDescription": "Ontvang een melding wanneer problemen die jij hebt gemeld, opgelost zijn.", + "components.NotificationTypeSelector.issuecomment": "Opmerking op probleem", + "components.NotificationTypeSelector.issueresolvedDescription": "Stuur meldingen wanneer problemen opgelost zijn.", + "components.ManageSlideOver.openarr4k": "Openen in 4K {arr}", + "components.NotificationTypeSelector.adminissuecommentDescription": "Ontvang een melding wanneer andere gebruikers reageren op problemen.", + "components.NotificationTypeSelector.userissuecommentDescription": "Ontvang een melding wanneer er nieuwe reacties komen op problemen die jij hebt gemeld.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Ontvang een melding wanneer andere gebruikers problemen melden.", + "components.NotificationTypeSelector.issuecreated": "Probleem gemeld", + "components.NotificationTypeSelector.issuecreatedDescription": "Stuur meldingen wanneer problemen worden gemeld.", + "components.NotificationTypeSelector.issueresolved": "Probleem opgelost", + "components.PermissionEdit.createissues": "Problemen melden", + "components.PermissionEdit.createissuesDescription": "Toestemming geven om mediaproblemen te melden.", + "components.PermissionEdit.manageissues": "Problemen beheren", + "components.PermissionEdit.manageissuesDescription": "Toestemming geven om mediaproblemen te beheren.", + "components.PermissionEdit.viewissuesDescription": "Toestemming geven om mediaproblemen te bekijken die door andere gebruikers zijn gemeld.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Maak een token aan in je accountinstellingen", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Toegangstoken", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Instellingen voor Pushbullet-meldingen konden niet opgeslagen worden.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Token toepassings-API", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Een toepassing registreren om te gebruiken met {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Je gebruikers- of groepsidentifier van 30 tekens", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Je moet een geldig toepassingstoken opgeven", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Instellingen voor Pushover-meldingen konden niet opgeslagen worden.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Instellingen voor Pushover-meldingen succesvol opgeslagen!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Je moet een toegangstoken opgeven", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Je moet een geldige gebruikers- of groepssleutel opgeven", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Instellingen voor Pushbullet-meldingen succesvol opgeslagen!", + "components.IssueDetails.playonplex": "Afspelen op Plex", + "components.IssueDetails.play4konplex": "Afspelen in 4K op Plex", + "components.IssueDetails.openin4karr": "Openen in 4K {arr}", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {aflevering} other {afleveringen}}", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {seizoen} other {seizoenen}}", + "components.ManageSlideOver.manageModalIssues": "Onopgeloste problemen", + "components.IssueModal.CreateIssueModal.extras": "Extra's", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Ontvang een melding wanneer problemen worden opgelost door andere gebruikers.", + "components.NotificationTypeSelector.issuereopened": "Probleem opnieuw ingediend", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Ontvang een melding wanneer problemen door andere gebruikers opnieuw worden ingediend.", + "components.NotificationTypeSelector.issuereopenedDescription": "Stuur meldingen wanneer problemen opnieuw worden ingediend.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Ontvang een bericht wanneer problemen die jij hebt gemeld, opnieuw worden ingediend.", + "components.RequestModal.requestseasons4k": "{seasonCount} {seasonCount, plural, one {seizoen} other {seizoenen}} in 4K aanvragen", + "components.RequestModal.requestmovies": "{count} {count, plural, one {film} other {films}} aanvragen", + "components.RequestModal.selectmovies": "Film(s) selecteren", + "components.MovieDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}", + "components.RequestModal.requestmovies4k": "{count} {count, plural, one {film} other {films}} in 4K aanvragen", + "components.TvDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}", + "components.IssueDetails.commentplaceholder": "Voeg een opmerking toe…" } diff --git a/src/i18n/locale/pl.json b/src/i18n/locale/pl.json index cd737acb5..d017c3975 100644 --- a/src/i18n/locale/pl.json +++ b/src/i18n/locale/pl.json @@ -1,3 +1,997 @@ { - "components.StatusBadge.status": "" + "components.StatusBadge.status": "{status}", + "components.AppDataWarning.dockerVolumeMissingDescription": "Montowanie woluminu {appDataPath} nie zostało poprawnie skonfigurowane. Wszystkie dane zostaną wyczyszczone po zatrzymaniu lub ponownym uruchomieniu kontenera.", + "components.Discover.DiscoverMovieGenre.genreMovies": "Filmy {genre}", + "components.Discover.popularmovies": "Popularne filmy", + "components.LanguageSelector.languageServerDefault": "Domyślny ({language})", + "components.Layout.VersionStatus.streamdevelop": "Overseerr wersja deweloperska", + "components.Layout.VersionStatus.streamstable": "Overseerr wersja stabilna", + "components.Login.email": "Adres e-mail", + "components.Login.forgotpassword": "Zapomniane hasło?", + "components.Login.password": "Hasło", + "components.Login.loginerror": "Coś poszło nie tak przy próbie logowania.", + "components.Login.signin": "Zaloguj", + "components.Login.signingin": "Logowanie…", + "components.Login.signinheader": "Zaloguj się aby kontynuować", + "components.Login.signinwithoverseerr": "Użyj swojego konta {applicationTitle}", + "components.IssueDetails.IssueComment.areyousuredelete": "Czy na pewno chcesz usunąć ten komentarz?", + "components.IssueDetails.IssueComment.delete": "Usuń komentarz", + "components.IssueDetails.IssueComment.edit": "Edytuj komentarz", + "components.IssueDetails.IssueComment.validationComment": "Musisz wpisać wiadomość", + "components.IssueDetails.openedby": "#{issueId} otwarty przez {username} {relativeTime}", + "components.IssueModal.CreateIssueModal.submitissue": "Prześlij problem", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Raport o problemie dla {title} przesłany pomyślnie!", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Coś poszło nie tak podczas zgłaszania problemu.", + "components.IssueDetails.IssueDescription.deleteissue": "Usuń problem", + "components.IssueDetails.IssueDescription.edit": "Edytuj opis", + "components.IssueDetails.allepisodes": "Wszystkie odcinki", + "components.IssueDetails.allseasons": "Wszystkie sezony", + "components.IssueDetails.closeissue": "Zamknij problem", + "components.IssueDetails.closeissueandcomment": "Zamknij z komentarzem", + "components.IssueDetails.comments": "Komentarze", + "components.IssueDetails.episode": "Odcinek {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Problem", + "components.IssueDetails.lastupdated": "Ostatnio zaktualizowane", + "components.IssueDetails.leavecomment": "Komentarz", + "components.IssueDetails.openinarr": "Otwórz w {arr}", + "components.IssueDetails.toasteditdescriptionfailed": "Coś poszło nie tak podczas edytowania opisu problemu.", + "components.IssueDetails.toastissuedeletefailed": "Podczas usuwania problemu coś poszło nie tak.", + "components.IssueDetails.toaststatusupdatefailed": "Coś poszło nie tak podczas aktualizowania stanu problemu.", + "components.IssueDetails.unknownissuetype": "Nieznany", + "components.ManageSlideOver.openarr": "Otwórz w {arr}", + "components.IssueModal.CreateIssueModal.extras": "Dodatki", + "components.IssueModal.CreateIssueModal.problemepisode": "Dotknięty odcinek", + "components.IssueModal.CreateIssueModal.problemseason": "Dotknięty sezon", + "components.IssueList.sortAdded": "Najnowsze", + "components.IssueModal.CreateIssueModal.toastviewissue": "Wyświetl problem", + "components.IssueModal.issueSubtitles": "Napisy", + "components.IssueModal.issueVideo": "Wideo", + "components.ManageSlideOver.manageModalIssues": "Otwarte problemy", + "components.ManageSlideOver.manageModalRequests": "Prośby", + "components.NotificationTypeSelector.mediarequested": "Żądane media", + "components.PermissionEdit.admin": "Admin", + "components.PermissionEdit.autoapprove4kMovies": "Automatyczne zatwierdzanie filmów 4K", + "components.IssueDetails.openin4karr": "Otwórz w 4K {arr}", + "components.IssueDetails.issuetype": "Typ", + "components.IssueDetails.nocomments": "Brak komentarzy.", + "components.IssueDetails.problemepisode": "Odcinek, którego dotyczy problem", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.issuetype": "Typ", + "components.IssueList.IssueItem.opened": "Otwarty", + "components.IssueList.IssueItem.openeduserdate": "{date} przez {user}", + "components.IssueList.IssueItem.problemepisode": "Odcinek, którego dotyczy problem", + "components.IssueList.IssueItem.unknownissuetype": "Nieznany", + "components.IssueModal.CreateIssueModal.episode": "Odcinek {episodeNumber}", + "components.IssueList.IssueItem.viewissue": "Zobacz problem", + "components.IssueList.issues": "Problemy", + "components.IssueList.showallissues": "Pokaż wszystkie problemy", + "components.IssueList.sortModified": "Ostatnio zmodyfikowane", + "components.IssueModal.CreateIssueModal.allepisodes": "Wszystkie odcinki", + "components.IssueModal.CreateIssueModal.allseasons": "Wszystkie sezony", + "components.Login.signinwithplex": "Użyj swojego konta Plex", + "components.Login.validationemailrequired": "Musisz podać prawidłowy adres e-mail", + "components.Login.validationpasswordrequired": "Musisz podać hasło", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Wszystkie sezony zostaną oznaczone jako dostępne.", + "components.ManageSlideOver.downloadstatus": "Stan pobierania", + "components.LanguageSelector.originalLanguageDefault": "Wszystkie języki", + "components.Layout.LanguagePicker.displaylanguage": "Język wyświetlania", + "components.Layout.SearchInput.searchPlaceholder": "Szukaj filmów i seriali", + "components.Layout.Sidebar.dashboard": "Odkryj", + "components.Layout.Sidebar.issues": "Problemy", + "components.Layout.Sidebar.requests": "Prośby", + "components.Layout.Sidebar.settings": "Ustawienia", + "components.Layout.Sidebar.users": "Użytkownicy", + "components.Layout.UserDropdown.myprofile": "Profil", + "components.Layout.UserDropdown.settings": "Ustawienia", + "components.Layout.UserDropdown.signout": "Wyloguj się", + "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commity}} za", + "components.Layout.VersionStatus.outofdate": "Nieaktualny", + "components.ManageSlideOver.manageModalClearMedia": "Wyczyść dane multimedialne", + "components.ManageSlideOver.manageModalNoRequests": "Brak próśb.", + "components.NotificationTypeSelector.issuecomment": "Komentarz do problemu", + "components.NotificationTypeSelector.issuecommentDescription": "Wysyłaj powiadomienia, gdy problemy otrzymają nowe komentarze.", + "components.NotificationTypeSelector.issuecreated": "Zgłoszono problem", + "components.NotificationTypeSelector.issuecreatedDescription": "Wysyłaj powiadomienia, gdy zostaną zgłoszone problemy.", + "components.NotificationTypeSelector.issueresolved": "Problem rozwiązany", + "components.NotificationTypeSelector.issueresolvedDescription": "Wysyłaj powiadomienia, gdy problemy zostaną rozwiązane.", + "components.NotificationTypeSelector.mediaAutoApproved": "Media automatycznie zatwierdzane", + "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Wysyłaj powiadomienia, gdy użytkownicy składają nowe prośby o media, które są automatycznie zatwierdzane.", + "components.NotificationTypeSelector.mediaapprovedDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia zostaną ręcznie zatwierdzone.", + "components.NotificationTypeSelector.mediaavailableDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia staną się dostępne.", + "components.NotificationTypeSelector.mediadeclinedDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia zostaną odrzucone.", + "components.NotificationTypeSelector.mediafailed": "Awaria multimediów", + "components.PermissionEdit.autoapproveSeriesDescription": "Przyznaj automatyczne zatwierdzanie próśb o filmy inne niż 4K.", + "components.PermissionEdit.createissues": "Zgłoś problemy", + "components.PermissionEdit.manageissues": "Zarządzaj problemami", + "components.PermissionEdit.manageissuesDescription": "Udziel uprawnień do zarządzania problemami z multimediami.", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Spowoduje to nieodwracalne usunięcie wszystkich danych dla {mediaType}, w tym wszelkie prośby. Jeśli ten element istnieje w Twojej bibliotece Plex, informacje o multimediach zostaną odtworzone podczas następnego skanowania.", + "components.IssueModal.CreateIssueModal.providedetail": "Podaj szczegółowe wyjaśnienie napotkanego problemu.", + "components.IssueModal.CreateIssueModal.whatswrong": "Co jest nie tak?", + "components.Discover.MovieGenreList.moviegenres": "Gatunki filmowe", + "components.Discover.TvGenreList.seriesgenres": "Gatunki seriali", + "components.Discover.noRequests": "Brak próśb.", + "components.Discover.recentrequests": "Ostatnie prośby", + "components.Discover.upcomingmovies": "Nadchodzące filmy", + "components.IssueModal.CreateIssueModal.reportissue": "Zgłoś problem", + "components.IssueModal.CreateIssueModal.season": "Sezon {seasonNumber}", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Musisz podać opis", + "components.IssueModal.issueAudio": "Audio", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Czy wystąpił problem z {title}?", + "components.IssueDetails.reopenissueandcomment": "Otwórz ponownie z komentarzem", + "components.IssueModal.issueOther": "Inny", + "components.CollectionDetails.numberofmovies": "{count} Filmy", + "components.Discover.discovermovies": "Popularne filmy", + "components.Discover.MovieGenreSlider.moviegenres": "Gatunki filmowe", + "components.Discover.upcoming": "Nadchodzące filmy", + "components.IssueDetails.problemseason": "Sezon, którego dotyczy problem", + "components.IssueDetails.season": "Sezon {seasonNumber}", + "components.IssueDetails.toastissuedeleted": "Pomyślnie usunięto problem!", + "components.CollectionDetails.overview": "Podsumowanie", + "components.Discover.StudioSlider.studios": "Studia", + "components.Discover.discover": "Odkryj", + "components.Discover.TvGenreSlider.tvgenres": "Gatunki seriali", + "components.Discover.recentlyAdded": "Niedawno dodane", + "components.IssueDetails.toasteditdescriptionsuccess": "Pomyślnie edytowano opis problemu!", + "components.IssueDetails.IssueDescription.description": "Opis", + "components.IssueDetails.toaststatusupdated": "Pomyślnie zaktualizowano status problemu!", + "components.IssueDetails.deleteissueconfirm": "Czy na pewno chcesz usunąć ten problem?", + "components.NotificationTypeSelector.userissuecommentDescription": "Otrzymuj powiadomienia, gdy problemy zgłoszone przez ciebie otrzymają nowe komentarze.", + "components.MovieDetails.overview": "Przegląd", + "components.NotificationTypeSelector.usermediaavailableDescription": "Otrzymuj powiadomienia, gdy Twoje prośby o multimedia staną się dostępne.", + "components.PermissionEdit.autoapprove4kSeriesDescription": "Przyznaj automatyczne zatwierdzanie próśb o seriale 4K.", + "components.IssueDetails.deleteissue": "Usuń problem", + "components.MovieDetails.budget": "Budżet", + "components.MovieDetails.mark4kavailable": "Oznacz jako dostępne w 4K", + "components.IssueDetails.play4konplex": "Odtwarzanie w 4K na platformie Plex", + "components.ManageSlideOver.movie": "film", + "components.IssueDetails.reopenissue": "Otwórz ponownie problem", + "components.MovieDetails.recommendations": "Zalecenia", + "components.PermissionEdit.autoapprove4kDescription": "Przyznaj automatyczne zatwierdzanie wszystkich próśb o media 4K.", + "components.MovieDetails.watchtrailer": "Obejrzyj zwiastun", + "components.NotificationTypeSelector.adminissuecommentDescription": "Otrzymuj powiadomienia, gdy inni użytkownicy skomentują problem.", + "components.MovieDetails.showless": "Pokaż mniej", + "components.MovieDetails.similar": "Podobne tytuły", + "components.ManageSlideOver.markavailable": "Oznacz jako dostępne", + "components.MediaSlider.ShowMoreCard.seemore": "Zobacz więcej", + "components.PermissionEdit.adminDescription": "Pełny dostęp administratora. Omija wszystkie inne kontrole uprawnień.", + "components.ManageSlideOver.manageModalTitle": "Zarządzaj {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Oznacz jako dostępne w 4K", + "components.NotificationTypeSelector.userissuecreatedDescription": "Otrzymuj powiadomienia, gdy inni użytkownicy zgłaszają problemy.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Otrzymuj powiadomienia, gdy zgłoszone przez ciebie problemy zostaną rozwiązane.", + "components.NotificationTypeSelector.usermediadeclinedDescription": "Otrzymuj powiadomienia, gdy Twoje prośby o multimedia zostaną odrzucone.", + "components.PermissionEdit.autoapprove4k": "Automatycznie zatwierdzaj 4K", + "components.ManageSlideOver.openarr4k": "Otwórz w 4K {arr}", + "components.ManageSlideOver.tvshow": "seria", + "components.MovieDetails.MovieCast.fullcast": "Pełna obsada", + "components.MovieDetails.cast": "Obsada", + "components.MovieDetails.markavailable": "Oznacz jako dostępne", + "components.MovieDetails.revenue": "Dochody", + "components.MovieDetails.originallanguage": "Język oryginału", + "components.MovieDetails.originaltitle": "Tytuł oryginalny", + "components.MovieDetails.overviewunavailable": "Przegląd niedostępny.", + "components.MovieDetails.showmore": "Pokaż więcej", + "components.NotificationTypeSelector.notificationTypes": "Typy powiadomień", + "components.NotificationTypeSelector.usermediaapprovedDescription": "Otrzymuj powiadomienia, gdy Twoje prośby o multimedia zostaną zatwierdzone.", + "components.NotificationTypeSelector.usermediarequestedDescription": "Otrzymuj powiadomienia, gdy inni użytkownicy prześlą nowe prośby o multimedia, które wymagają zatwierdzenia.", + "components.PermissionEdit.autoapprove": "Automatycznie zatwierdzaj", + "components.PermissionEdit.autoapproveMovies": "Automatyczne zatwierdzanie filmów", + "components.PermissionEdit.autoapproveMoviesDescription": "Przyznaj automatyczne zatwierdzanie próśb o filmy inne niż 4K.", + "components.PermissionEdit.requestTvDescription": "Udziel pozwolenia na przesyłanie próśb o seriale inne niż 4K.", + "components.PermissionEdit.settings": "Zarządzaj ustawieniami", + "components.PersonDetails.appearsin": "Wystąpienia", + "components.PersonDetails.alsoknownas": "Znany również jako: {names}", + "components.PlexLoginButton.signingin": "Logowanie…", + "components.PlexLoginButton.signinwithplex": "Zaloguj się", + "components.PermissionEdit.viewissuesDescription": "Przyznaj uprawnienia do przeglądania problemów z multimediami zgłoszonych przez innych użytkowników.", + "components.QuotaSelector.unlimited": "Bez ograniczeń", + "components.RegionSelector.regionDefault": "Wszystkie regiony", + "components.RegionSelector.regionServerDefault": "Domyślny ({region})", + "components.RequestBlock.profilechanged": "Profil jakości", + "components.RequestBlock.server": "Serwer docelowy", + "components.RequestBlock.requestoverrides": "Zastąpienia żądań", + "components.RequestBlock.rootfolder": "Folder główny", + "components.RequestButton.approverequest": "Zatwierdź prośbę", + "components.PersonDetails.lifespan": "{birthdate} - {deathdate}", + "components.RequestButton.requestmore": "Poproś o więcej", + "components.RequestCard.deleterequest": "Usuń prośbę", + "components.RequestButton.declinerequest": "Odrzuć prośbę", + "components.RequestButton.viewrequest": "Zobacz prośbę", + "components.RequestButton.requestmore4k": "Poproś o więcej w 4K", + "components.RequestModal.AdvancedRequester.notagoptions": "Brak tagów.", + "components.RequestList.sortModified": "Ostatnio zmodyfikowany", + "components.RequestModal.AdvancedRequester.advancedoptions": "Zaawansowane", + "components.RequestModal.AdvancedRequester.default": "{name} (Domyślnie)", + "components.RequestModal.AdvancedRequester.languageprofile": "Profil językowy", + "components.PermissionEdit.managerequestsDescription": "Przyznaj uprawnienia do zarządzania prośbami o multimedia. Wszystkie prośby złożone przez użytkownika z tym uprawnieniem zostaną automatycznie zatwierdzone.", + "components.PermissionEdit.request4k": "Poproś o 4K", + "components.PermissionEdit.request4kDescription": "Udziel zgody na przesyłanie próśb o multimedia 4K.", + "components.PermissionEdit.request4kMovies": "Poproś o filmy 4K", + "components.PermissionEdit.request4kMoviesDescription": "Udziel zgody na przesyłanie próśb o filmy 4K.", + "components.PermissionEdit.request4kTvDescription": "Udziel zgody na przesyłanie próśb o seriale 4K.", + "components.PermissionEdit.requestMovies": "Poproś o filmy", + "components.PermissionEdit.requestMoviesDescription": "Udziel pozwolenia na przesyłanie próśb o filmy inne niż 4K.", + "components.RequestList.RequestItem.cancelRequest": "Anuluj prośbę", + "components.RequestList.RequestItem.deleterequest": "Usuń prośbę", + "components.RequestList.RequestItem.editrequest": "Edytuj prośbę", + "components.RequestList.RequestItem.modified": "Zmodyfikowano", + "components.RequestList.RequestItem.modifieduserdate": "{date} przez {user}", + "components.RequestList.RequestItem.requested": "Prośba zgłoszona", + "components.RequestList.RequestItem.requesteddate": "Prośba zgłoszona", + "components.RequestList.showallrequests": "Pokaż wszystkie prośby", + "components.RequestList.sortAdded": "Najnowsze", + "components.RequestModal.AdvancedRequester.destinationserver": "Serwer docelowy", + "components.RequestModal.AdvancedRequester.qualityprofile": "Profil jakości", + "components.RequestModal.AdvancedRequester.rootfolder": "Folder główny", + "components.RequestModal.AdvancedRequester.selecttags": "Wybierz tagi", + "components.RequestModal.AdvancedRequester.tags": "Tagi", + "components.RequestModal.QuotaDisplay.movie": "film", + "components.PermissionEdit.viewrequests": "Wyświetl prośby", + "components.PermissionEdit.settingsDescription": "Przyznaj uprawnienia do modyfikowania ustawień globalnych. Użytkownik musi mieć to uprawnienie, aby przyznać je innym.", + "components.PermissionEdit.users": "Zarządzanie użytkownikami", + "components.PermissionEdit.viewissues": "Wyświetl problemy", + "components.PersonDetails.birthdate": "Urodzony {birthdate}", + "components.PermissionEdit.usersDescription": "Udziel uprawnień do zarządzania użytkownikami. Użytkownicy z tym uprawnieniem nie mogą modyfikować użytkowników z uprawnieniami administratora ani ich udzielać.", + "components.Discover.DiscoverMovieLanguage.languageMovies": "Filmy po {language}", + "components.Discover.DiscoverNetwork.networkSeries": "Seriale z {network}", + "components.Discover.DiscoverStudio.studioMovies": "Filmy studia {studio}", + "components.Discover.NetworkSlider.networks": "Platformy", + "components.Discover.discovertv": "Popularne Seriale", + "components.Discover.populartv": "Popularne seriale", + "components.Discover.trending": "Popularne", + "components.Discover.upcomingtv": "Nadchodzące seriale", + "components.DownloadBlock.estimatedtime": "Szacowany czas {time}", + "components.RequestModal.seasonnumber": "Sezon {number}", + "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON payload zresetowany pomyślnie!", + "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Template Variable Help", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Wysłano powiadomienie testowe webhook!", + "components.Settings.Notifications.encryptionOpportunisticTls": "Zawsze używaj STARTTLS", + "components.Settings.Notifications.pgpPrivateKey": "Klucz prywatny PGP", + "components.Settings.Notifications.sendSilently": "Wysyłaj po cichu", + "components.Settings.Notifications.sendSilentlyTip": "Wysyłaj powiadomienia bez dźwięku", + "components.Settings.Notifications.senderName": "Nazwa nadawcy", + "components.Settings.Notifications.smtpHost": "Host SMTP", + "components.Settings.Notifications.smtpPort": "Port SMTP", + "components.Settings.Notifications.telegramsettingsfailed": "Nie udało się zapisać ustawień powiadomień Telegram.", + "components.Settings.Notifications.telegramsettingssaved": "Ustawienia powiadomień Telegram zostały zapisane pomyślnie!", + "components.Settings.Notifications.toastDiscordTestFailed": "Nie udało się wysłać powiadomienia testowego Discord.", + "components.Settings.Notifications.toastDiscordTestSending": "Wysłanie powiadomienia testowego Discord…", + "components.Settings.Notifications.toastDiscordTestSuccess": "Powiadomienie testowe Discord wysłane!", + "components.Settings.Notifications.toastEmailTestFailed": "Nie udało się wysłać testowego powiadomienia e-mail.", + "components.Settings.Notifications.toastEmailTestSending": "Wysyłanie powiadomienia testowego e-mail…", + "components.Settings.Notifications.toastEmailTestSuccess": "Wysłano powiadomienie testowe e-mail!", + "components.Settings.Notifications.toastTelegramTestFailed": "Nie udało się wysłać powiadomienia testowego Telegram.", + "components.Settings.Notifications.toastTelegramTestSuccess": "Wysłano powiadomienie testowe Telegram!", + "components.Settings.RadarrModal.hostname": "Nazwa hosta lub adres IP", + "components.Settings.RadarrModal.servername": "Nazwa serwera", + "components.Settings.RadarrModal.ssl": "Użyj SSL", + "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "Musisz dostarczyć poprawny payload JSON", + "components.Settings.RadarrModal.add": "Dodaj serwer", + "components.Settings.RadarrModal.notagoptions": "Brak tagów.", + "components.Settings.RadarrModal.validationApplicationUrl": "Musisz podać poprawny adres URL", + "components.Settings.RadarrModal.loadingTags": "Ładowanie tagów…", + "components.PersonDetails.crewmember": "Ekipa", + "components.RequestButton.approverequest4k": "Zatwierdź prośbę 4K", + "components.RequestButton.declinerequest4k": "Odrzuć prośbę o 4K", + "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", + "components.RequestModal.requestseasons": "Poproś o {seasonCount} {seasonCount, plural, one {sezon} other {sezony}}", + "components.RequestModal.selectseason": "Wybierz sezon(y)", + "components.ResetPassword.gobacklogin": "Wróć do strony logowania", + "components.ResetPassword.requestresetlinksuccessmessage": "Link do resetowania hasła zostanie wysłany na podany adres e-mail, jeśli jest on powiązany z użytkownikiem.", + "components.ResetPassword.resetpassword": "Zresetuj swoje hasło", + "components.ResetPassword.validationemailrequired": "Musisz podać prawidłowy adres e-mail", + "components.ResetPassword.validationpasswordmatch": "Hasła muszą być zgodne", + "components.ResetPassword.validationpasswordminchars": "Hasło jest zbyt krótkie; powinno mieć co najmniej 8 znaków", + "components.ResetPassword.validationpasswordrequired": "Musisz podać hasło", + "components.Search.search": "Szukaj", + "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Włącz agenta", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Wymagane tylko wtedy, gdy nie używasz profilu default", + "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "Nie udało się zapisać ustawień powiadomień LunaSea.", + "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "Ustawienia powiadomień LunaSea zostały pomyślnie zapisane!", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "Nie udało się wysłać powiadomienia testowego LunaSea.", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "Wysyłanie powiadomienia testowego LunaSea…", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "Wysłano powiadomienie testowe LunaSea!", + "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "Musisz podać poprawny adres URL", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "URL Webhook", + "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Token dostępu (Access Token)", + "components.Settings.Notifications.NotificationsPushover.accessTokenTip": " Zarejestruj aplikację do użytku z Overseerr", + "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Nie udało się zapisać ustawień powiadomień Pushover.", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Ustawienia powiadomień Pushover zapisane pomyślnie!", + "components.Settings.Notifications.NotificationsPushover.userToken": "Klucz użytkownika lub grupy", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Nie udało się wysłać powiadomienia testowego Slack.", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSending": "Wysyłanie powiadomienia testowego Slack…", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Wysłano powiadomienie testowe Slack!", + "components.Settings.Notifications.NotificationsSlack.validationTypes": "Musisz wybrać co najmniej jeden typ powiadomienia", + "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "Musisz podać prawidłowy adres URL", + "components.Settings.Notifications.NotificationsSlack.webhookUrl": "URL Webhook", + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Utwórz integrację Incoming Webhook", + "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Włącz agenta", + "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Aby otrzymywać powiadomienia web push, Overseerr musi być obsługiwany przez HTTPS.", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Nie udało się wysłać powiadomienia testowego Web push.", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Wysyłanie powiadomia testowego web push…", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Powiadomienie testowe web push wysłane!", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Nie udało się zapisać ustawień powiadomień Web Push.", + "components.Settings.Notifications.NotificationsWebhook.authheader": "Nagłówek autoryzacji", + "components.Settings.Notifications.NotificationsWebhook.validationTypes": "Musisz wybrać co najmniej jeden typ powiadomienia", + "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "Musisz podać poprawny adres URL", + "components.Settings.Notifications.authUser": "Nazwa użytkownika SMTP", + "components.Settings.Notifications.botAPI": "Token autoryzacji bota", + "components.Settings.Notifications.botApiTip": "Stwórz bota do użycia z Overseerrem", + "components.Settings.Notifications.botAvatarUrl": "Adres URL awatara bota", + "components.Settings.Notifications.botUsername": "Nazwa użytkownika bota", + "components.Settings.Notifications.validationPgpPassword": "Musisz podać hasło PGP", + "components.Settings.Notifications.validationPgpPrivateKey": "Należy podać prawidłowy klucz prywatny PGP", + "components.Settings.Notifications.validationSmtpHostRequired": "Musisz podać prawidłową nazwę hosta lub adres IP", + "components.Settings.Notifications.webhookUrl": "URL Webhook", + "components.Settings.Notifications.webhookUrlTip": "Utwórz integrację webhook na swoim serwerze", + "components.Settings.Notifications.NotificationsWebhook.customJson": "Payload JSON", + "components.Settings.RadarrModal.baseUrl": "Baza URL", + "components.Settings.RadarrModal.selectMinimumAvailability": "Wybierz minimalną dostępność", + "components.Settings.RadarrModal.selectQualityProfile": "Wybierz profil jakości", + "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Musisz wybrać minimalną dostępność", + "components.Settings.RadarrModal.validationNameRequired": "Musisz podać nazwę serwera", + "components.Settings.RadarrModal.validationPortRequired": "Musisz podać prawidłowy numer portu", + "components.Settings.RadarrModal.validationRootFolderRequired": "Musisz wybrać folder główny", + "components.Settings.SettingsAbout.Releases.currentversion": "Aktualny", + "components.Settings.SettingsAbout.Releases.latestversion": "Najnowszy", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Dane o wydaniu są obecnie niedostępne.", + "components.Settings.SettingsAbout.Releases.releases": "Wydania", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Lista zmian", + "components.RequestModal.QuotaDisplay.season": "sezon", + "components.RequestModal.requestcancelled": "Prośba o {title} została anulowana.", + "components.Settings.Notifications.NotificationsLunaSea.validationTypes": "Musisz wybrać co najmniej jeden typ powiadomienia", + "components.RequestModal.requestSuccess": "Prośba o {title} wysłana pomyślnie!", + "components.RequestModal.season": "sezon", + "components.ResetPassword.emailresetlink": "Link do odzyskiwania przez adres e-mail", + "components.RequestModal.requestadmin": "Ta prośba zostanie zatwierdzona automatycznie.", + "components.ResetPassword.passwordreset": "Resetowanie hasła", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Powiadomienie testowe Pushbullet wysłane!", + "components.RequestModal.requesttitle": "Prośba o {title}", + "components.ResetPassword.confirmpassword": "Potwierdź hasło", + "components.ResetPassword.email": "Adres e-mail", + "components.ResetPassword.password": "Hasło", + "components.ResetPassword.resetpasswordsuccessmessage": "Hasło zostało zresetowane pomyślnie!", + "components.Settings.Notifications.NotificationsLunaSea.profileName": "Nazwa profilu", + "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "Musisz podać token dostępu", + "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Musisz wybrać co najmniej jeden typ powiadomienia", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Twój adres URL obiektu webhook powiadomienia oparty na użytkowniku lub urządzeniu", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Ustawienia powiadomień pushbullet zostały pomyślnie zapisane!", + "components.Settings.Notifications.NotificationsPushover.agentenabled": "Włącz agenta", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Powiadomienie testowe Pushover wysłane!", + "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Nie udało się zapisać ustawień powiadomień Slack.", + "components.Settings.Notifications.NotificationsPushover.validationTypes": "Musisz wybrać co najmniej jeden typ powiadomienia", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Ustawienia powiadomień web push zostały pomyślnie zapisane!", + "components.Discover.DiscoverTvLanguage.languageSeries": "{language} Serial", + "components.MovieDetails.runtime": "{minutes} minuty", + "components.IssueDetails.IssueComment.postedby": "Opublikowane przez {username} {relativeTime}", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Nie udało się wysłać powiadomienia testowego Pushover.", + "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Musisz podać prawidłowy token aplikacji", + "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Należy podać prawidłowy klucz użytkownika lub grupy", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Wysyłanie powiadomienia testowego Pushover…", + "components.Settings.Notifications.NotificationsPushover.userTokenTip": "Twój 30-znakowy identyfikator użytkownika lub grupy", + "components.Settings.Notifications.NotificationsSlack.agentenabled": "Włącz agenta", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Ustawienia powiadomień Slack zapisane pomyślnie!", + "components.Discover.DiscoverTvGenre.genreSeries": "Seriale {genre}", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Wysyłanie powiadomienia testowego webhook…", + "components.Settings.Notifications.botUsernameTip": "Pozwól użytkownikom również rozpocząć czat z botem i skonfigurować własne powiadomienia", + "components.Settings.Notifications.pgpPrivateKeyTip": "Podpisuj zaszyfrowane wiadomości e-mail za pomocą OpenPGP", + "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Włącz agenta", + "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Przywróć ustawienia domyślne", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Nie udało się wysłać powiadomienia testowego Webhook.", + "components.Settings.Notifications.encryptionTip": "W większości przypadków niejawny TLS używa portu 465, a STARTTLS używa portu 587", + "components.Settings.Notifications.pgpPassword": "Hasło PGP", + "components.Settings.Notifications.pgpPasswordTip": "Podpisuj zaszyfrowane wiadomości e-mail za pomocą OpenPGP", + "components.IssueDetails.IssueComment.postedbyedited": "Opublikowane przez {username} {relativeTime} (edytowano)", + "components.MovieDetails.play4konplex": "Odtwórz w 4K na Plex", + "components.MovieDetails.MovieCrew.fullcrew": "Pełna obsada", + "components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studia}}", + "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Wysyłaj powiadomienia, gdy inni użytkownicy składają nowe prośby o multimedia, które są automatycznie zatwierdzane.", + "components.Settings.RadarrModal.edit4kradarr": "Edytuj serwer 4K Radarr", + "components.Settings.RadarrModal.loadingprofiles": "Ładowanie profili jakości…", + "components.CollectionDetails.requestcollection4k": "Poproś o kolekcję w 4K", + "components.MovieDetails.streamingproviders": "Obecnie dostępne na", + "components.MovieDetails.playonplex": "Odtwórz na Plex", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Data wydania} other {Daty wydania}}", + "components.MovieDetails.viewfullcrew": "Zobacz pełną obsadę", + "components.PermissionEdit.autoapproveDescription": "Przyznaj automatyczne zatwierdzanie wszystkich próśb o multimedia inne niż 4K.", + "components.RequestButton.approve4krequests": "Zatwierdź {requestCount, plural, one {Prośba 4K} other {{requestCount} Prośby 4K }}", + "components.Settings.RadarrModal.minimumAvailability": "Minimalna dostępność", + "components.RequestButton.decline4krequests": "Odrzuć {requestCount, plural, one {prośby 4K} other {{requestCount} próśb 4K}}", + "components.NotificationTypeSelector.usermediafailedDescription": "Otrzymuj powiadomienia, gdy prośby o multimedia nie zostaną dodane do Radarr lub Sonarr.", + "components.PermissionEdit.advancedrequest": "Zaawansowane prośby", + "components.PermissionEdit.advancedrequestDescription": "Przyznaj uprawnienia do modyfikowania zaawansowanych opcji próśb multimedia.", + "components.PermissionEdit.autoapprove4kMoviesDescription": "Przyznaj automatyczne zatwierdzanie próśb o filmy 4K.", + "components.PermissionEdit.autoapprove4kSeries": "Automatyczne zatwierdzanie seriali 4K", + "components.PermissionEdit.autoapproveSeries": "Automatyczne zatwierdzanie seriali", + "components.QuotaSelector.seasons": "{count, plural, one {sezon} other {sezony}}", + "components.QuotaSelector.tvRequests": "{quotaLimit} {seasons} na{quotaDays} {days}", + "components.RequestBlock.seasons": "{seasonCount, plural, one {sezon} other {sezony}}", + "components.RequestButton.viewrequest4k": "Wyświetl prośbę 4K", + "components.RequestCard.failedretry": "Coś poszło nie tak podczas ponawiania prośby.", + "components.RequestButton.approverequests": "Zatwierdź {requestCount, plural, one {prośba} other {{requestCount} próśb}}", + "components.NotificationTypeSelector.mediarequestedDescription": "Wysyłaj powiadomienia, gdy użytkownicy składają nowe prośby dotyczące multimediów, które wymagają zatwierdzenia.", + "components.PermissionEdit.requestDescription": "Udzielenie zgody na składanie próśb na multimedia inne niż 4K.", + "components.PermissionEdit.viewrequestsDescription": "Przyznaj uprawnienia do przeglądania próśb o multimedia przesłanych przez innych użytkowników.", + "components.PersonDetails.ascharacter": "jako {character}", + "components.QuotaSelector.days": "{count, plural, one {dzień} other {dni}}", + "components.QuotaSelector.movieRequests": "{quotaLimit} {movies} na {quotaDays} {days}", + "components.QuotaSelector.movies": "{count, plural, one {film} other {filmy}}", + "components.RequestButton.declinerequests": "Odrzuć {requestCount, plural, one {prośbę} other {{requestCount} prośby}}", + "components.RequestCard.mediaerror": "Tytuł skojarzony z tą prośbą nie jest już dostępny.", + "components.RequestModal.QuotaDisplay.quotaLinkUser": "Możesz wyświetlić podsumowanie limitów próśb tego użytkownika na jego stronie profilu.", + "components.Settings.Notifications.validationSmtpPortRequired": "Musisz podać prawidłowy numer portu", + "components.Settings.RadarrModal.create4kradarr": "Dodaj nowy serwer 4K Radarr", + "components.Settings.RadarrModal.createradarr": "Dodaj nowy serwer Radarr", + "components.RequestModal.QuotaDisplay.quotaLink": "Możesz wyświetlić podsumowanie limitów próśb na swojej stronie profilu.", + "components.Settings.Notifications.validationTypes": "Musisz wybrać co najmniej jeden typ powiadomienia", + "components.Settings.Notifications.validationUrl": "Musisz podać prawidłowy adres URL", + "components.Settings.RadarrModal.default4kserver": "Domyślny serwer 4K", + "components.Settings.RadarrModal.loadingrootfolders": "Ładowanie folderów głównych…", + "components.Settings.RadarrModal.defaultserver": "Domyślny serwer", + "components.Settings.RadarrModal.validationProfileRequired": "Musisz wybrać profil jakości", + "components.IssueDetails.playonplex": "Odtwarzaj na Plex", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Odcinek} other {Odcinki}}", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {sezon} other {sezony}}", + "components.NotificationTypeSelector.mediaapproved": "Zatwierdzone media", + "components.NotificationTypeSelector.mediaavailable": "Multimedia dostępne", + "components.NotificationTypeSelector.mediadeclined": "Multimedia odrzucone", + "components.NotificationTypeSelector.mediafailedDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia nie zostaną dodane do Radarr lub Sonarr.", + "components.PermissionEdit.createissuesDescription": "Udzielanie zgody na zgłaszanie problemów z multimediami.", + "components.PermissionEdit.managerequests": "Zarządzaj prośbami", + "components.PermissionEdit.request": "Prośba", + "components.PermissionEdit.request4kTv": "Poproś o serial w 4K", + "components.PermissionEdit.requestTv": "Poproś o serial", + "components.RequestCard.seasons": "{seasonCount, plural, one {Sezon} other {Sezony}}", + "components.RequestList.RequestItem.failedretry": "Coś poszło nie tak podczas ponawiania prośby.", + "components.RequestList.RequestItem.mediaerror": "Tytuł powiązany z tą prośbą nie jest już dostępny.", + "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {sezon} other {sezony}}", + "components.RequestList.requests": "Prośby", + "components.RequestModal.AdvancedRequester.animenote": "* Ta seria to anime.", + "components.RequestModal.AdvancedRequester.requestas": "Poproś jako", + "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {Brak} other {#}} {type} {remaining, plural, one {prośba} other {próśb}} remaining", + "components.RequestModal.QuotaDisplay.requiredquota": "Aby przesłać prośbę o ten serial, musisz mieć co najmniej {seasons} {seasons, plural, one {prośbę} other {prośby}}.", + "components.RequestModal.QuotaDisplay.allowedRequests": "Możesz przesyłać{limit} próśb {type} co {days} dni.", + "components.RequestModal.QuotaDisplay.allowedRequestsUser": "Ten użytkownik może przeysłać {limit} próśb {type} co {days} dni.", + "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {film} other {filmy}}", + "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Brak wystarczającej liczby próśb o sezon", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "Aby przesłać prośbę o ten serial, ten użytkownik musi mieć co najmniej {seasons} {seasons, plural, one {prośbę} other {prośby}}.", + "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {sezon} other {sezony}}", + "components.RequestModal.SearchByNameModal.nosummary": "Nie znaleziono żadnych informacji o tym tytule.", + "components.RequestModal.SearchByNameModal.notvdbiddescription": "Nie mogliśmy automatycznie spełnić Twojej prośby. Wybierz odpowiednie dopasowanie z poniższej listy.", + "components.RequestModal.alreadyrequested": "Już poproszono", + "components.RequestModal.autoapproval": "Automatyczne zatwierdzenie", + "components.RequestModal.edit": "Edytuj prośbę", + "components.RequestModal.errorediting": "Coś poszło nie tak podczas edytowania prośby.", + "components.RequestModal.extras": "Dodatki", + "components.RequestModal.numberofepisodes": "Liczba odcinków", + "components.RequestModal.pending4krequest": "Oczekująca prośba o 4K dla {title}", + "components.RequestModal.pendingapproval": "Twoja prośba oczekuje na zatwierdzenie.", + "components.RequestModal.pendingrequest": "Oczekująca prośba o {title}", + "components.RequestModal.request4ktitle": "Poproś o {title} w 4K", + "components.RequestModal.requestCancel": "Prośba o {title} została anulowana.", + "components.RequestModal.requestedited": "Prośba o {title} została pomyślnie edytowana!", + "components.RequestModal.requesterror": "Coś poszło nie tak podczas przesyłania prośby.", + "components.RequestModal.requestfrom": "Prośba użytkownika {username} oczekuje na zatwierdzenie.", + "components.RequestModal.cancel": "Anuluj prośbę", + "components.Search.searchresults": "Wyniki wyszukiwania", + "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Utwórz token z poziomu Ustawień konta", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Nie udało się wysłać powiadomienia testowego Pushbullet.", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Wysyłanie powiadomienia testowego Pushbullet…", + "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Włącz agenta", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Nie udało się zapisać ustawień powiadomień Pushbullet.", + "components.Settings.Notifications.NotificationsPushover.accessToken": "Token API aplikacji", + "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "URL Webhook", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Nie udało się zapisać ustawień powiadomień webhook.", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Ustawienia powiadomień webhook zostały pomyślnie zapisane!", + "components.Settings.Notifications.agentenabled": "Włącz agenta", + "components.Settings.Notifications.allowselfsigned": "Zezwalaj na certyfikaty z podpisem własnym", + "components.Settings.Notifications.authPass": "Hasło SMTP", + "components.Settings.Notifications.encryptionNone": "Brak", + "components.Settings.Notifications.toastTelegramTestSending": "Wysyłanie powiadomia testowego Telegram…", + "components.Settings.Notifications.validationBotAPIRequired": "Musisz podać token autoryzacji bota", + "components.Settings.Notifications.validationChatIdRequired": "Musisz podać poprawny identyfikator czatu", + "components.Settings.Notifications.validationEmail": "Musisz podać poprawny adres e-mail", + "components.Settings.Notifications.chatId": "Identyfikator czatu (Chat ID)", + "components.Settings.Notifications.chatIdTip": "Rozpocznij czat ze swoim botem, dodaj @get_id_bot i wydaj polecenie /my_id", + "components.Settings.Notifications.discordsettingsfailed": "Nie udało się zapisać ustawień powiadomień Discord.", + "components.Settings.Notifications.discordsettingssaved": "Ustawienia powiadomień Discorda zapisane pomyślnie!", + "components.Settings.Notifications.emailsender": "Adres nadawcy", + "components.Settings.Notifications.emailsettingsfailed": "Nie udało się zapisać ustawień powiadomień e-mail.", + "components.Settings.Notifications.emailsettingssaved": "Ustawienia powiadomień e-mail zostały zapisane pomyślnie!", + "components.Settings.Notifications.encryption": "Metoda szyfrowania", + "components.Settings.Notifications.encryptionDefault": "Użyj STARTTLS, jeśli jest dostępny", + "components.Settings.Notifications.encryptionImplicitTls": "Używanie niejawnego protokołu TLS", + "components.Settings.RadarrModal.apiKey": "Klucz API", + "components.Settings.RadarrModal.port": "Port", + "components.Settings.RadarrModal.qualityprofile": "Profil jakości", + "components.Settings.RadarrModal.rootfolder": "Folder główny", + "components.Settings.RadarrModal.selectRootFolder": "Wybierz folder główny", + "components.Settings.RadarrModal.selecttags": "Wybierz tagi", + "components.Settings.RadarrModal.server4k": "Serwer 4K", + "components.Settings.RadarrModal.testFirstQualityProfiles": "Połączenie testowe w celu załadowania profili jakości", + "components.Settings.RadarrModal.testFirstRootFolders": "Połączenie testowe w celu załadowania folderów głównych", + "components.Settings.RadarrModal.testFirstTags": "Połączenie testowe, aby wczytać tagi", + "components.Settings.RadarrModal.toastRadarrTestFailure": "Nie udało się połączyć z Radarr.", + "components.Settings.RadarrModal.editradarr": "Edytuj serwer Radarr", + "components.Settings.RadarrModal.enableSearch": "Włącz automatyczne wyszukiwanie", + "components.Settings.RadarrModal.externalUrl": "Zewnętrzny adres URL", + "components.Settings.RadarrModal.syncEnabled": "Włącz skanowanie", + "components.Settings.RadarrModal.tags": "Tagi", + "components.Settings.RadarrModal.toastRadarrTestSuccess": "Połączenie z Radarr nawiązane pomyślnie!", + "components.Settings.RadarrModal.validationApiKeyRequired": "Musisz podać klucz API", + "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "Adres URL nie może kończyć się ukośnikiem", + "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Podstawowy adres URL musi zaczynać się ukośnikiem", + "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Bazowy adres URL nie może być zakończony ukośnikiem", + "components.Settings.RadarrModal.validationHostnameRequired": "Musisz podać prawidłową nazwę hosta lub adres IP", + "components.Settings.SettingsJobsCache.editJobSchedule": "Zmodyfikuj zadanie", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Częstotliwość", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Co{jobScheduleHours, plural, one {godzinę} other {{jobScheduleHours} godzin}}", + "components.Settings.SettingsJobsCache.flushcache": "Opróżnij pamięć podręczną", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Co {jobScheduleMinutes, plural, one {minutę} other {{jobScheduleMinutes} minut}}", + "components.Settings.SettingsJobsCache.jobs": "Zadania", + "components.Settings.SettingsJobsCache.jobstarted": "{jobname} rozpoczęte.", + "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Skanowanie ostatnio dodanych na Plex", + "components.Settings.SettingsJobsCache.process": "Proces", + "components.Settings.SettingsJobsCache.radarr-scan": "Skan Radarr", + "components.Settings.SettingsJobsCache.sonarr-scan": "Skanowanie Sonarr", + "components.Settings.SettingsJobsCache.runnow": "Uruchom teraz", + "components.Settings.SettingsJobsCache.unknownJob": "Nieznane zadanie", + "components.Settings.SettingsLogs.copiedLogMessage": "Skopiowano komunikat dziennika do schowka.", + "components.Settings.SettingsLogs.extraData": "Dodatkowe dane", + "components.Settings.SettingsLogs.filterDebug": "Debug", + "components.Settings.SettingsLogs.filterInfo": "Informacje", + "components.Settings.SettingsLogs.filterWarn": "Ostrzeżenie", + "components.Settings.SettingsLogs.level": "Powaga", + "components.Settings.SettingsLogs.logDetails": "Szczegóły dziennika", + "components.Settings.SettingsLogs.label": "Etykieta", + "components.Settings.SettingsLogs.logs": "Logi", + "components.Settings.SettingsLogs.pauseLogs": "Pauza", + "components.Settings.SettingsLogs.resumeLogs": "Wznów", + "components.Settings.SettingsLogs.showall": "Pokaż wszystkie logi", + "components.Settings.SettingsLogs.time": "Sygnatura czasowa", + "components.Settings.SettingsUsers.defaultPermissions": "Domyślne uprawnienia", + "components.Settings.SettingsUsers.localLogin": "Włącz lokalne logowanie", + "components.Settings.SettingsUsers.localLoginTip": "Zezwalaj użytkownikom na logowanie się przy użyciu adresu e-mail i hasła, a nie Plex OAuth", + "components.Settings.SettingsUsers.movieRequestLimitLabel": "Globalny limit próśb o filmy", + "components.Settings.SettingsAbout.totalrequests": "Łączna liczba próśb", + "components.Settings.SettingsAbout.version": "Wersja", + "components.Settings.SettingsJobsCache.cache": "Pamięć podręczna", + "components.Settings.SettingsAbout.Releases.viewchangelog": "Zobacz dziennik zmian", + "components.Settings.SettingsAbout.Releases.viewongithub": "Zobacz na GitHub", + "components.Settings.SettingsAbout.about": "O", + "components.Settings.SettingsAbout.betawarning": "To jest oprogramowanie BETA. Funkcje mogą być uszkodzone i/lub niestabilne. Zgłaszaj wszelkie problemy na GitHub!", + "components.Settings.SettingsAbout.documentation": "Dokumentacja", + "components.Settings.SettingsAbout.gettingsupport": "Uzyskiwanie pomocy technicznej", + "components.Settings.SettingsAbout.githubdiscussions": "Dyskusje na GitHubie", + "components.Settings.SettingsAbout.helppaycoffee": "Pomóż zapłacić za kawę", + "components.Settings.SettingsAbout.outofdate": "Nieaktualne", + "components.Settings.SettingsAbout.overseerrinformation": "O Overseerr", + "components.Settings.SettingsAbout.preferredmethod": "Preferowane", + "components.Settings.SettingsAbout.runningDevelop": "Korzystasz z gałęzi develop Overseerr, która jest zalecana tylko dla osób przyczyniających się do rozwoju lub pomagających w testach.", + "components.Settings.SettingsAbout.supportoverseerr": "Wesprzyj Overseerr", + "components.Settings.SettingsAbout.timezone": "Strefa czasowa", + "components.Settings.SettingsAbout.totalmedia": "Multimedia ogółem", + "components.Settings.SettingsAbout.uptodate": "Aktualne", + "components.Settings.SettingsJobsCache.cacheDescription": "Overseerr buforuje żądania do zewnętrznych punktów końcowych API, aby zoptymalizować wydajność i uniknąć wykonywania niepotrzebnych wywołań API.", + "components.Settings.SettingsJobsCache.cacheflushed": "Pamięć podręczna {cachename} została opróżniona.", + "components.Settings.SettingsJobsCache.cachehits": "Trafienia", + "components.Settings.SettingsJobsCache.cachekeys": "Klucze ogółem", + "components.Settings.SettingsJobsCache.canceljob": "Anuluj zadanie", + "components.Settings.SettingsJobsCache.command": "Polecenie", + "components.Settings.SettingsJobsCache.download-sync": "Synchronizuj pobierania", + "components.Settings.SettingsJobsCache.download-sync-reset": "Zresetuj synchronizację pobierania", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Coś poszło nie tak podczas zapisywania zadania.", + "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr wykonuje pewne zadania konserwacyjne jako regularnie zaplanowane zadania, ale mogą być one również uruchamiane ręcznie poniżej. Ręczne uruchomienie zadania nie spowoduje zmiany jego harmonogramu.", + "components.Settings.SettingsJobsCache.nextexecution": "Następne wykonanie", + "components.Settings.SettingsLogs.logsDescription": "Możesz również zobaczyć te logi bezpośrednio przez stdout, lub w {configDir}/logs/overseerr.log.", + "components.Settings.SettingsJobsCache.jobname": "Nazwa zadania", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Zadanie edytowane pomyślnie!", + "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} anulowane.", + "components.Settings.SettingsJobsCache.jobsandcache": "Zadania i pamięć podręczna", + "components.Settings.SettingsJobsCache.jobtype": "Typ", + "components.Settings.SettingsLogs.copyToClipboard": "Skopiuj do schowka", + "components.Settings.SettingsUsers.newPlexLogin": "Włącz funkcję New Plex Sign-In", + "components.Settings.SettingsUsers.toastSettingsSuccess": "Ustawienia użytkownika zostały zapisane pomyślnie!", + "components.Settings.SettingsJobsCache.cacheksize": "Rozmiar klucza", + "components.Settings.SettingsJobsCache.cachemisses": "Chybienia", + "components.Settings.SettingsJobsCache.cachename": "Nazwa pamięci podręcznej", + "components.Settings.SettingsJobsCache.cachevsize": "Rozmiar wartości", + "components.Settings.SettingsJobsCache.plex-full-scan": "Pełne skanowanie biblioteki Plex", + "components.Settings.SettingsLogs.filterError": "Błąd", + "components.Settings.SettingsLogs.message": "Wiadomość", + "components.Settings.SettingsUsers.defaultPermissionsTip": "Początkowe uprawnienia nadawane nowym użytkownikom", + "components.Settings.SettingsUsers.newPlexLoginTip": "Zezwalaj użytkownikom Plex na logowanie się bez wcześniejszego importowania", + "components.Settings.SettingsUsers.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.", + "components.Settings.SettingsUsers.tvRequestLimitLabel": "Globalny limit żądań serii", + "components.Settings.SettingsUsers.userSettings": "Ustawienia użytkownika", + "components.Settings.SettingsUsers.users": "Użytkownicy", + "components.Settings.SonarrModal.animeTags": "Tagi anime", + "components.Settings.SonarrModal.animelanguageprofile": "Profil języka anime", + "components.Settings.SonarrModal.animequalityprofile": "Profil jakości anime", + "components.Settings.SonarrModal.baseUrl": "Baza URL", + "components.Settings.SonarrModal.create4ksonarr": "Dodaj nowy serwer 4K Sonarr", + "components.Settings.SonarrModal.createsonarr": "Dodaj nowy serwer Sonarr", + "components.Settings.SonarrModal.edit4ksonarr": "Edytuj serwer 4K Sonarr", + "components.Settings.SonarrModal.editsonarr": "Edytuj serwer Sonarr", + "components.Settings.SonarrModal.loadingprofiles": "Ładowanie profili jakości…", + "components.Settings.SonarrModal.notagoptions": "Brak tagów.", + "components.Settings.SonarrModal.port": "Port", + "components.Settings.SonarrModal.seasonfolders": "Foldery sezonów", + "components.Settings.SonarrModal.testFirstQualityProfiles": "Przetestuj połączenie, aby załadować profile jakości", + "components.Settings.SonarrModal.testFirstRootFolders": "Połączenie testowe w celu załadowania folderów głównych", + "components.Settings.SonarrModal.tags": "Tagi", + "components.Settings.SonarrModal.testFirstTags": "Przetestuj połączenie, aby załadować tagi", + "components.Settings.SonarrModal.toastSonarrTestFailure": "Nie udało się połączyć z Sonarrem.", + "components.Settings.SonarrModal.testFirstLanguageProfiles": "Przetestuj połączenie, aby załadować profile językowe", + "components.Settings.SonarrModal.validationApplicationUrl": "Musisz podać poprawny adres URL", + "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Podstawowy adres URL musi mieć wiodący ukośnik", + "components.Settings.SonarrModal.validationLanguageProfileRequired": "Musisz wybrać profil językowy", + "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Bazowy adres URL nie może być zakończony ukośnikiem", + "components.Settings.SonarrModal.validationHostnameRequired": "Musisz podać prawidłową nazwę hosta lub adres IP", + "components.Settings.SonarrModal.validationPortRequired": "Musisz podać prawidłowy numer portu", + "components.Settings.SonarrModal.validationProfileRequired": "Musisz wybrać profil jakości", + "components.Settings.SonarrModal.validationNameRequired": "Musisz podać nazwę serwera", + "components.Settings.SonarrModal.validationRootFolderRequired": "Musisz wybrać folder główny", + "components.Settings.activeProfile": "Aktywny profil", + "components.Settings.applicationTitle": "Tytuł aplikacji", + "components.Settings.applicationurl": "Adres URL aplikacji", + "components.Settings.cacheImages": "Włącz buforowanie obrazów", + "components.Settings.csrfProtection": "Włącz ochronę CSRF", + "components.Settings.copied": "Skopiowano klucz API do schowka.", + "components.Settings.csrfProtectionHoverTip": "NIE włączaj tego ustawienia, chyba że rozumiesz, co robisz!", + "components.Settings.default": "Domyślny", + "components.Settings.default4k": "Domyślne 4K", + "components.Settings.deleteserverconfirm": "Czy na pewno chcesz usunąć ten serwer?", + "components.Settings.email": "E-mail", + "components.Settings.enablessl": "Użyj SSL", + "components.Settings.generalsettingsDescription": "Skonfiguruj globalne i domyślne ustawienia dla Overseerr.", + "components.Settings.hideAvailable": "Ukryj dostępne multimedia", + "components.Settings.generalsettings": "Ustawienia ogólne", + "components.Settings.librariesRemaining": "Pozostałe biblioteki: {count}", + "components.Settings.hostname": "Nazwa hosta lub adres IP", + "components.Settings.is4k": "4K", + "components.Settings.locale": "Język wyświetlania", + "components.Settings.manualscan": "Ręczne skanowanie biblioteki", + "components.Settings.manualscanDescription": "Zwykle będzie to uruchamiane tylko raz na 24 godziny. Overseerr sprawdzi ostatnio dodane serwery Plex bardziej agresywnie. Jeśli konfigurujesz Plex po raz pierwszy, zalecane jest jednorazowe pełne ręczne skanowanie biblioteki!", + "components.Settings.mediaTypeMovie": "film", + "components.Settings.mediaTypeSeries": "serial", + "components.Settings.menuAbout": "O", + "components.Settings.menuGeneralSettings": "Ogólne", + "components.Settings.menuJobs": "Zadania i pamięć podręczna", + "components.Settings.noDefault4kServer": "Serwer 4K {serverType} musi być oznaczony jako domyślny, aby umożliwić użytkownikom składanie żądań 4K {mediaType}.", + "components.Settings.noDefaultNon4kServer": "Jeśli masz tylko jeden serwer {serverType} dla zawartości nie 4K i 4K (lub jeśli tylko pobierasz zawartość 4K), Twój serwer {serverType} powinien być NIE oznaczony jako serwer 4K.", + "components.Settings.noDefaultServer": "Co najmniej jeden serwer {serverType} musi być oznaczony jako domyślny, aby żądania {mediaType} były przetwarzane.", + "components.Settings.notificationAgentSettingsDescription": "Skonfiguruj i włącz agentów powiadomień.", + "components.Settings.notifications": "Powiadomienia", + "components.Settings.notificationsettings": "Ustawienia powiadomień", + "components.Settings.notrunning": "Nie działa", + "components.Settings.originallanguage": "Odkryj język", + "components.Settings.originallanguageTip": "Filtruj zawartość według języka oryginału", + "components.Settings.plexlibraries": "Biblioteki Plex", + "components.Settings.plexlibrariesDescription": "Biblioteki, które Overseerr skanuje w poszukiwaniu tytułów. Skonfiguruj i zapisz ustawienia połączenia Plex, a następnie kliknij przycisk poniżej, jeśli na liście nie ma żadnych bibliotek.", + "components.Settings.plexsettings": "Ustawienia Plex", + "components.Settings.radarrsettings": "Ustawienia Radarr", + "components.Settings.region": "Odkryj Region", + "components.Settings.regionTip": "Filtruj zawartość według dostępności regionalnej", + "components.Settings.scanning": "Synchronizacja…", + "components.Settings.serverLocal": "lokalny", + "components.Settings.serverRemote": "zdalny", + "components.Settings.serverSecure": "bezpieczne", + "components.Settings.serverpreset": "Serwer", + "components.Settings.serverpresetLoad": "Naciśnij przycisk, aby załadować dostępne serwery", + "components.Settings.serverpresetManualMessage": "Ręczna konfiguracja", + "components.Settings.serverpresetRefreshing": "Pobieranie serwerów…", + "components.Settings.services": "Usługi", + "components.Settings.settingUpPlexDescription": "Aby skonfigurować aplikację Plex, możesz wprowadzić szczegóły ręcznie lub wybrać serwer pobrany z witryny plex.tv. Naciśnij przycisk znajdujący się po prawej stronie listy rozwijanej, aby wyświetlić listę dostępnych serwerów.", + "components.Settings.toastPlexRefresh": "Pobieranie listy serwerów z Plex…", + "components.Settings.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.", + "components.Settings.validationHostnameRequired": "Musisz podać prawidłową nazwę hosta lub adres IP", + "components.Settings.validationApplicationUrlTrailingSlash": "Adres URL nie może kończyć się ukośnikiem", + "components.Settings.webAppUrl": " Adres URL aplikacji internetowej", + "components.Settings.validationWebAppUrl": "Musisz podać prawidłowy adres URL Plex Web App", + "components.Setup.finishing": "Kończenie…", + "components.Setup.signinMessage": "Zacznij logując się na swoje konto Plex", + "components.Setup.tip": "Wskazówka", + "components.Setup.welcome": "Witamy w Overseerr", + "components.StatusBadge.status4k": "4K {status}", + "components.StatusChacker.newversionDescription": "Overseerr został zaktualizowany! Kliknij przycisk poniżej, aby ponownie załadować stronę.", + "components.TvDetails.anime": "Anime", + "components.TvDetails.nextAirDate": "Następna data emisji", + "components.UserList.bulkedit": "Masowa edycja", + "components.UserList.create": "Utwórz", + "components.UserList.created": "Dołączył", + "components.UserList.edituser": "Edytuj uprawnienia użytkownika", + "components.UserList.email": "Adres e-mail", + "components.UserList.usercreatedsuccess": "Pomyślnie utworzono użytkownika!", + "components.UserList.userdeleted": "Użytkownik został pomyślnie usunięty!", + "components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Wyświetlana nazwa", + "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Zastąp globalny limit", + "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Użytkownik lokalny", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Domyślny ({language})", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Ustawienia powiadomień Discord zostały pomyślnie zapisane!", + "components.UserProfile.UserSettings.UserNotificationSettings.email": "E-mail", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Wysyłaj powiadomienia bez dźwięku", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Identyfikator czatu", + "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Potwierdź hasło", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Ustawienia powiadomień web push zostały pomyślnie zapisane!", + "i18n.noresults": "Brak wyników.", + "i18n.notrequested": "Brak próśb", + "i18n.partiallyavailable": "Częściowo dostępne", + "i18n.retry": "Ponów próbę", + "i18n.view": "Zobacz", + "pages.errormessagewithcode": "{statusCode} - {error}", + "pages.internalservererror": "Wewnętrzny błąd serwera", + "pages.oops": "Ups", + "pages.pagenotfound": "Nie znaleziono strony", + "components.CollectionDetails.requestcollection": "Poproś o kolekcję", + "components.Setup.setup": "Konfiguracja", + "i18n.close": "Zamknij", + "components.Settings.webpush": "Web Push", + "components.UserList.accounttype": "Typ", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filtruj zawartość według języka oryginału", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Rozpocznij czat ze swoim botem, dodaj @get_id_bot i wydaj polecenie /my_id", + "i18n.usersettings": "Ustawienia użytkownika", + "pages.returnHome": "Powrót do domu", + "pages.serviceunavailable": "Usługa niedostępna", + "components.TvDetails.overview": "Podsumowanie", + "components.UserList.admin": "Administrator", + "components.UserList.userdeleteerror": "Coś poszło nie tak podczas usuwania użytkownika.", + "components.UserList.userfail": "Coś poszło nie tak podczas zapisywania uprawnień użytkownika.", + "components.UserProfile.ProfileHeader.profile": "Zobacz profil", + "components.UserProfile.ProfileHeader.settings": "Edytuj ustawienia", + "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Limit próśb o serial", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Ustawienia zostały zapisane pomyślnie!", + "components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Powiadomienia", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Ustawienia powiadomień Telegram zostały zapisane pomyślnie!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Musisz podać prawidłowy identyfikator użytkownika", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Musisz podać prawidłowy klucz publiczny PGP", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "Uprawnienia zostały pomyślnie zapisane!", + "components.UserProfile.UserSettings.menuChangePass": "Hasło", + "components.UserProfile.movierequests": "Prośby o filmy", + "i18n.canceling": "Anulowanie…", + "i18n.resolved": "Rozwiązane", + "i18n.resultsperpage": "Wyświetlaj {pageSize} wyników na stronę", + "i18n.retrying": "Ponawianie…", + "i18n.status": "Stan", + "components.Settings.SonarrModal.hostname": "Nazwa hosta lub adres IP", + "components.Settings.SonarrModal.languageprofile": "Profil języka", + "components.Settings.SonarrModal.loadingTags": "Ładowanie tagów…", + "components.Settings.addsonarr": "Dodaj serwer Sonarr", + "components.Settings.plexsettingsDescription": "Skonfiguruj ustawienia serwera Plex. Overseerr skanuje biblioteki Plex, aby określić dostępność treści.", + "components.Settings.port": "Port", + "components.Settings.scan": "Synchronizuj biblioteki", + "components.Settings.toastApiKeySuccess": "Nowy klucz API został pomyślnie wygenerowany!", + "components.Settings.toastPlexConnecting": "Próba połączenia z Plex…", + "components.Settings.toastPlexConnectingFailure": "Nie udało się połączyć z Plex.", + "components.Settings.validationApplicationTitle": "Należy podać tytuł aplikacji", + "components.Settings.validationApplicationUrl": "Musisz podać poprawny adres URL", + "components.Settings.validationPortRequired": "Musisz podać prawidłowy numer portu", + "components.Settings.webhook": "Webhooki", + "components.Setup.configureplex": "Skonfiguruj Plex", + "components.Setup.configureservices": "Skonfiguruj usługi", + "components.Setup.continue": "Kontynuuj", + "components.StatusChacker.newversionavailable": "Aktualizacja aplikacji", + "components.StatusChacker.reloadOverseerr": "Odśwież", + "components.TvDetails.TvCast.fullseriescast": "Pełna obsada serialu", + "components.TvDetails.TvCrew.fullseriescrew": "Pełna ekipa serialu", + "components.TvDetails.episodeRuntime": "Czas trwania odcinka", + "components.TvDetails.originallanguage": "Język oryginalny", + "components.TvDetails.originaltitle": "Tytuł oryginalny", + "components.TvDetails.overviewunavailable": "Podsumowanie niedostępne.", + "components.TvDetails.playonplex": "Odtwarzanie na Plex", + "components.TvDetails.play4konplex": "Odtwarzanie w 4K na Plex", + "components.TvDetails.recommendations": "Rekommendacje", + "components.TvDetails.seasons": "{seasonCount, plural, one {# Sezon} other {# Sezony}}", + "components.TvDetails.showtype": "Typ serialu", + "components.TvDetails.similar": "Podobne seriale", + "components.TvDetails.streamingproviders": "Obecnie dostępne na", + "components.UserList.autogeneratepassword": "Automatycznie generuj hasło", + "components.UserList.autogeneratepasswordTip": "Wyślij do użytkownika wiadomość e-mail z hasłem wygenerowanym przez serwer", + "components.UserList.createlocaluser": "Utwórz użytkownika lokalnego", + "components.UserList.creating": "Tworzenie…", + "components.UserList.deleteuser": "Usuń użytkownika", + "components.UserList.displayName": "Wyświetlana nazwa", + "components.UserList.importedfromplex": "{userCount, plural, one {# nowy użytkownik} other {# nowych użytkowników}} został(o) zaimportowany(ch) z Plex pomyślnie!", + "components.UserList.localLoginDisabled": "Ustawienie Włącz Lokalne Logowanie jest obecnie wyłączone.", + "components.UserList.localuser": "Użytkownik lokalny", + "components.UserList.nouserstoimport": "Brak nowych użytkowników do zaimportowania z Plex.", + "components.UserList.owner": "Właściciel", + "components.UserList.password": "Hasło", + "components.UserList.passwordinfodescription": "Skonfiguruj adres URL aplikacji i włącz powiadomienia e-mail, aby umożliwić automatyczne generowanie hasła.", + "components.UserList.plexuser": "Użytkownik Plex", + "components.UserList.role": "Rola", + "components.UserList.sortRequests": "Liczba próśb", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Musisz podać token dostępu", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Musisz podać prawidłowy token aplikacji", + "components.UserProfile.recentrequests": "Ostatnie prośby", + "components.Settings.SonarrModal.enableSearch": "Włącz automatyczne wyszukiwanie", + "components.Settings.menuLogs": "Logi", + "components.Settings.SonarrModal.externalUrl": "Zewnętrzny adres URL", + "components.Settings.apikey": "Klucz API", + "components.Settings.general": "Ogólne", + "components.Settings.menuNotifications": "Powiadomienia", + "components.Settings.plex": "Plex", + "components.UserList.users": "Użytkownicy", + "components.UserList.user": "Użytkownik", + "components.UserList.usercreatedfailed": "Coś poszło nie tak podczas tworzenia użytkownika.", + "components.UserList.usercreatedfailedexisting": "Podany adres e-mail jest już używany przez innego użytkownika.", + "components.UserList.userlist": "Lista użytkowników", + "components.UserList.userssaved": "Uprawnienia użytkownika zostały pomyślnie zapisane!", + "components.UserList.validationEmail": "Musisz podać poprawny adres e-mail", + "components.UserList.validationpasswordminchars": "Hasło jest zbyt krótkie; powinno mieć co najmniej 8 znaków", + "components.UserProfile.ProfileHeader.joindate": "Dołączył {joindate}", + "components.UserProfile.ProfileHeader.userid": "ID użytkownika: {userid}", + "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Limit próśb o filmy", + "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Właściciel", + "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Użytkownik Plex", + "components.UserProfile.UserSettings.UserGeneralSettings.region": "Odkryj region", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Odkryj język", + "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filtruj zawartość według dostępności regionalnej", + "components.UserProfile.UserSettings.UserGeneralSettings.role": "Rola", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Nie udało się zapisać ustawień powiadomień Discord.", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Nie udało się zapisać ustawień powiadomień e-mail.", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Ustawienia powiadomień e-mail zostały zapisane pomyślnie!", + "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Ustawienia powiadomień", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "Klucz publiczny PGP", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Szyfruj wiadomości e-mail za pomocą OpenPGP", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Token dostępowy", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Utwórz token z poziomu ustawień konta", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Nie udało się zapisać ustawień powiadomień Pushbullet.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Ustawienia powiadomień pushbullet zostały pomyślnie zapisane!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Token interfejsu API aplikacji", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Zarejestruj aplikację do użycia z {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Klucz użytkownika lub grupy", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Twój 30-znakowy identyfikator użytkownika lub grupy", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Nie udało się zapisać ustawień powiadomień Pushover.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Ustawienia powiadomień pushover zostały pomyślnie zapisane!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Musisz podać prawidłowy klucz użytkownika lub grupy", + "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Musisz podać prawidłowy identyfikator czatu", + "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Nie udało się zapisać ustawień powiadomień Web Push.", + "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Aktualne hasło", + "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "Nowe hasło", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Hasło zostało zapisane pomyślnie!", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "Musisz potwierdzić nowe hasło", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPasswordSame": "Hasła muszą być zgodne", + "components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "Musisz podać swoje aktualne hasło", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Musisz podać nowe hasło", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPasswordLength": "Hasło jest zbyt krótkie; powinno mieć co najmniej 8 znaków", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.", + "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Nie możesz modyfikować własnych uprawnień.", + "components.UserProfile.UserSettings.menuGeneralSettings": "Ogólne", + "components.UserProfile.UserSettings.menuNotifications": "Powiadomienia", + "components.UserProfile.UserSettings.menuPermissions": "Uprawnienia", + "components.UserProfile.UserSettings.unauthorizedDescription": "Nie masz uprawnień do modyfikowania ustawień tego użytkownika.", + "components.UserProfile.limit": "{remaining} z {limit}", + "components.UserProfile.norequests": "Brak próśb.", + "components.UserProfile.pastdays": "{type} (ostatnie {days} dni)", + "components.UserProfile.requestsperdays": "Pozostało {limit}", + "components.UserProfile.seriesrequest": "Prośby o seriale", + "components.UserProfile.unlimited": "Bez ograniczeń", + "i18n.advanced": "Zaawansowane", + "i18n.all": "Wszystkie", + "i18n.approve": "Zatwierdź", + "i18n.areyousure": "Czy na pewno?", + "i18n.back": "Powrót", + "i18n.decline": "Odrzuć", + "i18n.declined": "Odrzucony", + "i18n.delete": "Usuń", + "i18n.deleting": "Usuwanie…", + "i18n.delimitedlist": "{a}, {b}", + "i18n.edit": "Edytuj", + "i18n.experimental": "Eksperymentalne", + "i18n.failed": "Nieudane", + "i18n.loading": "Ładowanie…", + "i18n.movie": "Film", + "i18n.movies": "Filmy", + "i18n.next": "Następny", + "i18n.open": "Otwarte", + "i18n.pending": "Oczekujący", + "i18n.previous": "Poprzedni", + "i18n.processing": "Przetwarzanie", + "i18n.request": "Poproś", + "i18n.request4k": "Poproś o 4K", + "i18n.requested": "Prośba zgłoszona", + "i18n.requesting": "Prośba…", + "i18n.save": "Zapisz zmiany", + "i18n.saving": "Zapisywanie…", + "i18n.settings": "Ustawienia", + "i18n.showingresults": "Wyświetlanie wyników od{from} do {to} z {total}", + "i18n.test": "Test", + "i18n.testing": "Testowanie…", + "i18n.tvshow": "Serial", + "i18n.tvshows": "Seriale", + "i18n.unavailable": "Niedostępny", + "pages.somethingwentwrong": "Coś poszło nie tak", + "components.Settings.SonarrModal.defaultserver": "Domyślny serwer", + "components.Settings.SonarrModal.selectRootFolder": "Wybierz folder główny", + "components.Settings.SonarrModal.add": "Dodaj serwer", + "components.Settings.SonarrModal.loadingrootfolders": "Ładowanie folderów głównych…", + "components.Settings.SonarrModal.qualityprofile": "Profil jakości", + "components.Settings.SonarrModal.rootfolder": "Folder główny", + "components.Settings.address": "Adres", + "components.Settings.SonarrModal.ssl": "Użyj SSL", + "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "Adres URL nie może kończyć się ukośnikiem", + "components.Settings.SettingsUsers.userSettingsDescription": "Konfiguruje globalne i domyślne ustawienia użytkowników.", + "components.Settings.SonarrModal.animerootfolder": "Folder główny anime", + "components.Settings.SonarrModal.selectQualityProfile": "Wybierz profil jakości", + "components.Settings.SonarrModal.selecttags": "Wybierz tagi", + "components.Settings.csrfProtectionTip": "Ustaw zewnętrzny dostęp api na tylko do odczytu (wymaga HTTPS, a Overseerr musi zostać ponownie załadowany, aby zmiany zostały wprowadzone)", + "components.Settings.toastPlexRefreshFailure": "Nie udało się pobrać listy serwerów Plex.", + "components.Settings.SonarrModal.apiKey": "Klucz API", + "components.Settings.cancelscan": "Anuluj skanowanie", + "components.Settings.SonarrModal.default4kserver": "Domyślny serwer 4K", + "components.Settings.SonarrModal.loadinglanguageprofiles": "Ładowanie profili językowych…", + "components.Settings.SonarrModal.selectLanguageProfile": "Wybierz profil językowy", + "components.Settings.SonarrModal.server4k": "Serwer 4K", + "components.Settings.SonarrModal.toastSonarrTestSuccess": "Połączenie z Sonarr nawiązane pomyślnie!", + "components.Settings.SonarrModal.validationApiKeyRequired": "Musisz podać klucz API", + "components.Settings.addradarr": "Dodaj serwer Radarr", + "components.Settings.SonarrModal.servername": "Nazwa serwera", + "components.Settings.SonarrModal.syncEnabled": "Włącz skanowanie", + "components.Settings.cacheImagesTip": "Optymalizacja i przechowywanie wszystkich obrazów lokalnie (zużywa znaczną ilość miejsca na dysku)", + "components.Settings.menuPlexSettings": "Plex", + "components.Settings.menuUsers": "Użytkownicy", + "components.Settings.currentlibrary": "Bieżąca biblioteka: {name}", + "components.Settings.menuServices": "Usługi", + "components.Settings.partialRequestsEnabled": "Zezwalaj na prośby o część serialu", + "components.Settings.sonarrsettings": "Ustawienia Sonarr", + "components.Settings.ssl": "Protokół SSL", + "components.Settings.trustProxyTip": "Pozwól Overseerr poprawnie rejestrować adresy IP klientów za serwerem proxy (Overseerr musi zostać ponownie załadowany, aby zmiany zostały wprowadzone)", + "components.Settings.toastPlexConnectingSuccess": "Połączenie Plex nawiązane pomyślnie!", + "components.Settings.toastSettingsSuccess": "Ustawienia zostały zapisane pomyślnie!", + "components.Settings.serviceSettingsDescription": "Skonfiguruj poniżej swój serwer(y) {serverType}. Możesz podłączyć wiele serwerów {serverType}, ale tylko dwa z nich mogą być oznaczone jako domyślne (jeden nie-4K i jeden 4K). Administratorzy mogą zmienić serwer używany do przetwarzania nowych żądań przed zatwierdzeniem.", + "components.Settings.toastApiKeyFailure": "Coś poszło nie tak podczas generowania nowego klucza API.", + "components.Settings.toastPlexRefreshSuccess": "Lista serwerów Plex została pobrana pomyślnie!", + "components.Settings.trustProxy": "Włącz obsługę proxy", + "components.Settings.startscan": "Rozpocznij skanowanie", + "components.Setup.finish": "Zakończ konfigurację", + "components.Setup.scanbackground": "Skanowanie będzie działać w tle. W międzyczasie możesz kontynuować proces konfiguracji.", + "components.TvDetails.network": "{networkCount, plural, one {Sieć} other {Sieci}}", + "components.UserProfile.UserSettings.UserGeneralSettings.user": "Użytkownik", + "components.Settings.webAppUrlTip": "Opcjonalnie kieruj użytkowników do aplikacji internetowej na Twoim serwerze zamiast do \"hostowanej\" aplikacji internetowej", + "components.Setup.loginwithplex": "Zaloguj się przez Plex", + "components.TvDetails.episodeRuntimeMinutes": "{runtime} minut", + "components.TvDetails.viewfullcrew": "Zobacz pełną ekipę", + "components.UserProfile.UserSettings.UserGeneralSettings.general": "Ogólne", + "components.TvDetails.firstAirDate": "Pierwsza data emisji", + "components.UserList.deleteconfirm": "Czy na pewno chcesz usunąć tego użytkownika? Wszystkie jego dane zostaną trwale usunięte.", + "components.TvDetails.cast": "Obsada", + "components.TvDetails.watchtrailer": "Obejrzyj zwiastun", + "components.UserList.sortCreated": "Data dołączenia", + "components.UserList.importfromplexerror": "Coś poszło nie tak podczas importowania użytkowników z Plex.", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.", + "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "Identyfikator użytkownika", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Wyślij po cichu", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Nie udało się zapisać ustawień powiadomień telegram.", + "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "Numer ID dla twojego konta użytkownika", + "components.UserList.importfromplex": "Importuj użytkowników z Plex", + "i18n.available": "Dostępny", + "components.UserList.sortDisplayName": "Wyświetlana nazwa", + "components.UserList.totalrequests": "Prośby", + "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Administrator", + "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Język wyświetlania", + "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Ustawienia ogólne", + "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Typ konta", + "components.UserProfile.UserSettings.UserPermissions.permissions": "Uprawnienia", + "components.UserProfile.totalrequests": "Łączna liczba próśb", + "i18n.cancel": "Anuluj", + "i18n.approved": "Zatwierdzone", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "To konto użytkownika nie ma obecnie ustawionego hasła. Skonfiguruj hasło poniżej, aby umożliwić temu kontu logowanie się jako \"użytkownik lokalny\"", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Twoje konto nie ma obecnie ustawionego hasła. Skonfiguruj hasło poniżej, aby umożliwić zalogowanie się jako \"użytkownik lokalny\" przy użyciu Twojego adresu e-mail.", + "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "Nie masz uprawnień do zmiany hasła tego użytkownika.", + "components.UserProfile.UserSettings.UserPasswordChange.password": "Hasło", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania hasła.", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Coś poszło nie tak podczas zapisywania hasła. Czy aktualne hasło zostało wpisane poprawnie?", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Otrzymuj powiadomienia, gdy problemy zostaną rozwiązane przez innych użytkowników.", + "components.NotificationTypeSelector.issuereopened": "Problem ponownie otwarty", + "components.NotificationTypeSelector.userissuereopenedDescription": "Otrzymuj powiadomienia o ponownym otwarciu zgłoszonych problemów.", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Otrzymuj powiadomienia, gdy problemy zostaną ponownie otwarte przez innych użytkowników.", + "components.NotificationTypeSelector.issuereopenedDescription": "Wysyłaj powiadomienia, gdy problemy zostaną ponownie otwarte.", + "components.RequestModal.requestmovies4k": "Prośba o {count} {count, plural, one {film} other {filmów}} w 4k", + "components.RequestModal.selectmovies": "Wybierz film(y)", + "components.RequestModal.requestmovies": "Prośba o {count} {count, plural, one {film} other {filmów}}", + "components.IssueDetails.commentplaceholder": "Dodaj komentarz…", + "components.MovieDetails.productioncountries": "Produkcja {countryCount, plural, one {kraj} other {kraje}}", + "components.RequestModal.requestseasons4k": "Prośba o {seasonCount} {seasonCount, plural, one {sezon} other {sezony}} w 4K", + "components.TvDetails.productioncountries": "Produkcja {countryCount, plural, one {kraj} other {kraje}}" } diff --git a/src/i18n/locale/pt_BR.json b/src/i18n/locale/pt_BR.json index 5d18ad2d1..ff6ddf17e 100644 --- a/src/i18n/locale/pt_BR.json +++ b/src/i18n/locale/pt_BR.json @@ -12,11 +12,6 @@ "components.MovieDetails.overviewunavailable": "Sinopse indisponível.", "components.MovieDetails.overview": "Sinopse", "components.MovieDetails.originallanguage": "Língua Original", - "components.MovieDetails.manageModalTitle": "Gerenciar Filme", - "components.MovieDetails.manageModalRequests": "Solicitações", - "components.MovieDetails.manageModalNoRequests": "Nenhuma solicitação.", - "components.MovieDetails.manageModalClearMediaWarning": "* Isso irá remover em definitivo todos dados deste filme, incluindo todas solicitações. Se este item existir em sua biblioteca do Plex, os dados de mídia serão recriados no próximo escaneamento.", - "components.MovieDetails.manageModalClearMedia": "Limpar Dados de Mídia", "components.MovieDetails.cast": "Elenco", "components.MovieDetails.budget": "Orçamento", "components.MovieDetails.MovieCast.fullcast": "Elenco Completo", @@ -70,7 +65,7 @@ "components.Settings.SonarrModal.validationProfileRequired": "Você deve selecionar um perfil de qualidade", "components.Settings.SonarrModal.validationPortRequired": "Você deve prover uma porta válida", "components.Settings.SonarrModal.validationNameRequired": "Você deve prover o nome do servidor", - "components.Settings.SonarrModal.validationHostnameRequired": "Você deve prover o Nome ou IP do Servidor", + "components.Settings.SonarrModal.validationHostnameRequired": "Você deve prover o nome ou IP do servidor", "components.Settings.SonarrModal.validationApiKeyRequired": "Você deve prover uma chave de API", "components.Settings.SonarrModal.testFirstRootFolders": "Teste conexão para carregar as pastas raízes", "components.Settings.SonarrModal.testFirstQualityProfiles": "Teste conexão para carregar perfis de qualidade", @@ -105,7 +100,7 @@ "components.Settings.RadarrModal.validationPortRequired": "Você deve prover uma porta válida", "components.Settings.RadarrModal.validationNameRequired": "Você deve prover o nome do servidor", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Você deve selecionar a disponibilidade mínima", - "components.Settings.RadarrModal.validationHostnameRequired": "Você deve prover o Nome ou IP do Servidor", + "components.Settings.RadarrModal.validationHostnameRequired": "Você deve prover o nome ou IP do servidor", "components.Settings.RadarrModal.validationApiKeyRequired": "Você deve prover uma chave de API", "components.Settings.RadarrModal.toastRadarrTestSuccess": "Conexão com Radarr estabelecida com sucesso!", "components.Settings.RadarrModal.toastRadarrTestFailure": "Falha ao conectar-se ao Radarr.", @@ -181,7 +176,7 @@ "components.Settings.SettingsAbout.Releases.viewchangelog": "Ver Mudanças", "components.Settings.SettingsAbout.Releases.versionChangelog": "Mudanças em {version}", "components.Settings.SettingsAbout.Releases.releases": "Versões", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informações de versão indisponíveis. O GitHub está indisponível?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informações de versão indisponíveis.", "components.Settings.SettingsAbout.Releases.latestversion": "Última Versão", "components.Settings.SettingsAbout.Releases.currentversion": "Atual", "components.TvDetails.recommendations": "Recomendações", @@ -189,10 +184,6 @@ "components.TvDetails.overview": "Sinopse", "components.TvDetails.originallanguage": "Língua original", "components.TvDetails.network": "{networkCount, plural, one {Emissora} other {Emissoras}}", - "components.TvDetails.manageModalTitle": "Gerenciar Série", - "components.TvDetails.manageModalRequests": "Solicitações", - "components.TvDetails.manageModalNoRequests": "Nenhuma solicitação.", - "components.TvDetails.manageModalClearMedia": "Limpar Dados de Mídia", "components.TvDetails.cast": "Elenco", "components.TvDetails.anime": "Animes", "components.TvDetails.TvCast.fullseriescast": "Elenco Completo da Série", @@ -224,14 +215,12 @@ "components.UserList.totalrequests": "Solicitações", "components.UserList.role": "Privilégio", "components.UserList.plexuser": "Usuário Plex", - "components.UserList.lastupdated": "Atualizado", "components.UserList.deleteuser": "Deletar Usuário", "components.UserList.deleteconfirm": "Tem certeza que deseja deletar esse usuário? Todas informações de solicitações feitas por esse usuário serão permanentemente removidas.", "components.UserList.created": "Criado", "components.UserList.admin": "Administrador", "components.TvDetails.similar": "Séries Semelhantes", "components.TvDetails.showtype": "Tipo de Série", - "components.TvDetails.manageModalClearMediaWarning": "* Isso irá remover em definitivo todos dados desta série, incluindo todas solicitações. Se este item existir em sua biblioteca do Plex, as informações de mídia serão recriadas no próximo escaneamento.", "components.RequestModal.requestseasons": "Solicitar {seasonCount} {seasonCount, plural, one {Temporada} other {Temporadas}}", "components.TvDetails.viewfullcrew": "Ver Toda Equipe Técnica", "components.TvDetails.TvCrew.fullseriescrew": "Equipe Técnica Completa da Série", @@ -244,9 +233,7 @@ "components.Settings.Notifications.NotificationsSlack.agentenabled": "Habilitar Agente", "components.RequestList.RequestItem.failedretry": "Algo deu errado ao retentar fazer a solicitação.", "components.MovieDetails.watchtrailer": "Assistir Trailer", - "components.CollectionDetails.requestswillbecreated": "Serão feitas solitações para os seguintes títulos:", "components.CollectionDetails.requestcollection": "Solicitar Coleção", - "components.CollectionDetails.requestSuccess": "{title} solictiado com sucesso!", "components.CollectionDetails.overview": "Sinopse", "components.CollectionDetails.numberofmovies": "Filmes: {count}", "i18n.requested": "Solicitado", @@ -276,7 +263,7 @@ "components.NotificationTypeSelector.mediafailedDescription": "Envia notificaões quando solicitações de mídia falharem ao serem adicionadas ao Radarr ou Sonarr.", "components.NotificationTypeSelector.mediafailed": "Solicitação Falhou", "components.NotificationTypeSelector.mediaavailableDescription": "Envia notificações quando mídias solicitadas estiverem disponíveis.", - "components.NotificationTypeSelector.mediaapprovedDescription": "Envia notificações quando solicitações de mídia são aprovadas manualmente.", + "components.NotificationTypeSelector.mediaapprovedDescription": "Enviar notificações quando solicitações de mídia são aprovadas manualmente.", "i18n.request": "Solicitar", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Você deve prover uma chave válida de usúario ou grupo", "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Você deve prover uma chave válida de acesso", @@ -286,7 +273,7 @@ "components.Settings.Notifications.NotificationsPushover.agentenabled": "Habilitar Agente", "components.Settings.Notifications.NotificationsPushover.accessToken": "Token de Acesso", "components.RequestList.sortModified": "Última Mudança", - "components.RequestList.sortAdded": "Data de Solicitação", + "components.RequestList.sortAdded": "Mais Recente", "components.RequestList.showallrequests": "Exibir Todas Solicitações", "components.StatusBadge.status4k": "4K {status}", "components.RequestModal.request4ktitle": "Solicitar {title} em 4K", @@ -375,9 +362,9 @@ "components.PlexLoginButton.signinwithplex": "Entrar", "components.Login.signingin": "Autenticando…", "components.PlexLoginButton.signingin": "Autenticando…", - "components.PermissionEdit.usersDescription": "Concede permissão para gerenciar usuários do Overseerr. Usuários com essa permissão não podem modificar usuários com acesso Administrativo, ou condecer tal permissão.", + "components.PermissionEdit.usersDescription": "Concede permissão para gerenciar usuários. Usuários com essa permissão não podem modificar usuários com acesso Administrativo, ou condecer tal permissão.", "components.PermissionEdit.users": "Gerenciar Usuários", - "components.PermissionEdit.settingsDescription": "Concede permissão para modificar configurações do Overseerr. O usuário precisar ter essa permissão para concedê-la a outros.", + "components.PermissionEdit.settingsDescription": "Concede permissão para modificar configurações globais. O usuário precisar ter essa permissão para concedê-la a outros.", "components.PermissionEdit.settings": "Gerenciar Configurações", "components.PermissionEdit.requestDescription": "Concede permissão para solicitar mídia não 4K.", "components.PermissionEdit.request4kTvDescription": "Concede permissão para solicitar séries em 4K.", @@ -387,15 +374,15 @@ "components.PermissionEdit.request4kDescription": "Concede permissão para solicitar mídia em 4K.", "components.PermissionEdit.request4k": "Solicitar 4K", "components.PermissionEdit.request": "Solicitar", - "components.PermissionEdit.managerequestsDescription": "Concede permissão para gerenciar solicitações no Overseerr. Todas solicitações feitas por um usuário com esse perfil serão automaticamente aprovadas.", + "components.PermissionEdit.managerequestsDescription": "Concede permissão para gerenciar solicitações de mídia. Todas solicitações feitas por um usuário com esse perfil serão automaticamente aprovadas.", "components.PermissionEdit.managerequests": "Gerenciar Solicitações", "components.PermissionEdit.autoapproveSeriesDescription": "Concede aprovação automática para solicitações de séries não 4K.", "components.PermissionEdit.autoapproveSeries": "Aprovar Séries Automaticamente", "components.PermissionEdit.autoapproveMoviesDescription": "Concede aprovação automática para solicitações de filmes não 4K.", "components.PermissionEdit.autoapproveMovies": "Aprovar Filmes Automaticamente", - "components.PermissionEdit.autoapproveDescription": "Concede aprovação automática para todas solicitações não 4K.", + "components.PermissionEdit.autoapproveDescription": "Concede aprovação automática para todas solicitações de mídia não 4K.", "components.PermissionEdit.autoapprove": "Aprovar Automaticamente", - "components.PermissionEdit.advancedrequestDescription": "Concede permissão para fazer solicitações avançadas.", + "components.PermissionEdit.advancedrequestDescription": "Concede permissão para alterar solicitações avançadas de mídia.", "components.PermissionEdit.advancedrequest": "Solicitações Avançadas", "components.PermissionEdit.adminDescription": "Acesso administrativo completo. Ignora todas outras checagens de privilégios.", "components.PermissionEdit.admin": "Administrador", @@ -403,23 +390,14 @@ "components.Login.signinheader": "Autentique para continuar", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Conexão com Sonarr estabelecida com sucesso!", "components.Settings.SonarrModal.toastSonarrTestFailure": "Falha ao conectar-se ao Sonarr.", - "components.TvDetails.opensonarr4k": "Abrir Série no Sonarr 4K", - "components.TvDetails.opensonarr": "Abrir Série no Sonarr", - "components.TvDetails.downloadstatus": "Estado do Download", "components.Settings.SonarrModal.syncEnabled": "Habilitar Escaneamento", "components.Settings.SonarrModal.externalUrl": "URL Externa", "components.Settings.RadarrModal.syncEnabled": "Habilitar Escaneamento", "components.Settings.RadarrModal.externalUrl": "URL Externa", - "components.MovieDetails.openradarr4k": "Abrir Filme no Radarr 4K", - "components.MovieDetails.openradarr": "Abrir Filme no Radarr", - "components.MovieDetails.downloadstatus": "Estado do Download", "components.TvDetails.playonplex": "Assitir no Plex", "components.TvDetails.play4konplex": "Assistir em 4K no Plex", "components.MovieDetails.playonplex": "Assistir no Plex", "components.MovieDetails.play4konplex": "Assistir em 4K no Plex", - "components.TvDetails.markavailable": "Marcar como Disponível", - "components.TvDetails.mark4kavailable": "Marcar como Disponível em 4K", - "components.TvDetails.allseasonsmarkedavailable": "* Todas temporadas serão marcadas como disponíveis.", "components.MovieDetails.markavailable": "Marcar como Disponível", "components.MovieDetails.mark4kavailable": "Marcar como Disponível em 4K", "components.Settings.trustProxy": "Habilitar Suporte a Proxy", @@ -462,7 +440,7 @@ "components.Settings.SonarrModal.validationApplicationUrl": "Você deve prover uma URL válida", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "A URL não pode terminar com uma barra", "components.Settings.RadarrModal.validationApplicationUrl": "Você deve prover uma URL válida", - "components.PermissionEdit.viewrequestsDescription": "Concede permissão para visualizar solicitações de outros usuários.", + "components.PermissionEdit.viewrequestsDescription": "Concede permissão para visualizar solicitações de mídia feita por outros usuários.", "components.PermissionEdit.viewrequests": "Visualizar Solicitações", "components.UserList.validationEmail": "Você deve prover um e-mail válido", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "URL Base não deve terminar com uma barra", @@ -495,7 +473,6 @@ "components.RequestModal.AdvancedRequester.languageprofile": "Perfil de Idioma", "components.Settings.Notifications.sendSilentlyTip": "Envia notificações sem som", "components.Settings.Notifications.sendSilently": "Enviar Silenciosamente", - "components.UserList.sortUpdated": "Última Atualização", "components.UserList.sortRequests": "Número de Solicitações", "components.UserList.sortDisplayName": "Nome de Exibição", "components.UserList.sortCreated": "Data de Criação", @@ -504,7 +481,7 @@ "components.PermissionEdit.autoapprove4kMoviesDescription": "Concede aprovação automática para solicitações de filmes em 4K.", "components.PermissionEdit.autoapprove4kSeries": "Aprovar Automaticamente Séries em 4K", "components.PermissionEdit.autoapprove4kMovies": "Aprovar Automaticamente Filmes em 4K", - "components.PermissionEdit.autoapprove4kDescription": "Concede aprovação automática para todas solicitações em 4K.", + "components.PermissionEdit.autoapprove4kDescription": "Concede aprovação automática para todas solicitações de mídia em 4K.", "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "Você deve prover um token de acesso", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Configurações de notificação via Pushbullet salvas com sucesso!", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Falha ao salvar configurações de notificação via Pushover.", @@ -545,7 +522,6 @@ "components.Layout.UserDropdown.myprofile": "Perfil", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Você deve prover um ID válido de usuário", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "O ID correspondente ao seu usuário", - "components.CollectionDetails.requestswillbecreated4k": "Serão feitas solicitações em 4K dos seguintes títulos:", "components.CollectionDetails.requestcollection4k": "Solicitar Coleção em 4K", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Região de Exploração", "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Idioma de Exploração", @@ -625,7 +601,6 @@ "components.Discover.MovieGenreSlider.moviegenres": "Gêneros de Filmes", "components.Discover.MovieGenreList.moviegenres": "Gêneros de Filmes", "components.Settings.partialRequestsEnabled": "Permitir Solicitações Parciais de Séries", - "components.RequestModal.requestall": "Solicitar Todas as Temporadas", "components.RequestModal.alreadyrequested": "Já Solicitado", "pages.errormessagewithcode": "{statusCode} - {error}", "components.ResetPassword.passwordreset": "Redefinir Senha", @@ -689,7 +664,7 @@ "i18n.canceling": "Cancelando…", "i18n.back": "Voltar", "i18n.areyousure": "Você tem certeza?", - "i18n.all": "Todas", + "i18n.all": "Todas(os)", "components.UserProfile.requestsperdays": "{limit} restante(s)", "components.UserProfile.unlimited": "Ilimitadas", "components.UserProfile.totalrequests": "Total de Solicitações", @@ -841,7 +816,7 @@ "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "Falha ao enviar notificação de teste via LunaSea.", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "Configurações de notificação via LunaSea salvas com sucesso!", "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "Falha ao salvar configurações de notificação via LunaSea.", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Necessário apenas quando não estiver usando o perfil padrão", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Necessário apenas quando não estiver usando o perfil default", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Nome de Perfil", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Habilitar Agente", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Habilitar Agente", @@ -881,9 +856,144 @@ "components.MovieDetails.showless": "Mostrar Menos", "components.TvDetails.streamingproviders": "Em Exibição na", "components.MovieDetails.streamingproviders": "Em Exibição na", - "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Tarefa editada com sucesso!", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Tarefa modificada com sucesso!", "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frequência", "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "A cada {jobScheduleHours, plural, one {hora} other {{jobScheduleHours} horas}}", "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "A cada {jobScheduleMinutes, plural, one {minuto} other {{jobScheduleMinutes} minutos}}", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.areyousuredelete": "Você tem certeza que deseja apagar este comentário?", + "components.IssueDetails.IssueComment.delete": "Apagar Comentário", + "components.IssueDetails.IssueComment.edit": "Editar Comentário", + "components.IssueDetails.IssueComment.postedby": "Feito {relativeTime} por {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Feito {relativeTime} por {username} (Edited)", + "components.IssueDetails.IssueComment.validationComment": "Você deve escrever uma mensagem", + "components.IssueDetails.closeissueandcomment": "Fechar com Comentário", + "components.IssueDetails.comments": "Comentários", + "components.IssueDetails.episode": "Episódio {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Problema", + "components.IssueDetails.issuetype": "Tipo", + "components.IssueDetails.IssueDescription.deleteissue": "Apagar Problema", + "components.IssueDetails.IssueDescription.description": "Descrição", + "components.IssueDetails.deleteissueconfirm": "Você tem certeza que deseja apagar este problema?", + "components.IssueDetails.nocomments": "Nenhum comentário.", + "components.IssueDetails.allepisodes": "Todos Episódios", + "components.IssueDetails.IssueDescription.edit": "Alterar Descrição", + "components.IssueDetails.allseasons": "Todas Temporadas", + "components.IssueDetails.leavecomment": "Comentar", + "components.IssueDetails.closeissue": "Encerrar Problema", + "components.IssueDetails.deleteissue": "Apagar Problema", + "components.IssueDetails.lastupdated": "Última Atualização", + "components.IssueDetails.openedby": "#{issueId} aberto {relativeTime} por {username}", + "components.IssueDetails.openinarr": "Abrir no {arr}", + "components.IssueDetails.reopenissue": "Re-abrir Problema", + "components.IssueDetails.problemepisode": "Episódio Afetado", + "components.IssueDetails.problemseason": "Temporada Afetada", + "components.IssueDetails.reopenissueandcomment": "Re-abrir com Comentário", + "components.IssueDetails.toasteditdescriptionfailed": "Algo deu errado ao editar a descrição do problema.", + "components.IssueDetails.toastissuedeleted": "Problema apagado com sucesso!", + "components.IssueDetails.openin4karr": "Abrir em {arr} 4K", + "components.IssueDetails.playonplex": "Assistir no Plex", + "components.IssueDetails.season": "Temporada {seasonNumber}", + "components.IssueDetails.toasteditdescriptionsuccess": "Descrição do problema alterada com sucesso!", + "components.IssueDetails.play4konplex": "Assistir em 4K no Plex", + "components.IssueDetails.toastissuedeletefailed": "Algo deu errado ao apagar problema.", + "components.IssueDetails.toaststatusupdated": "Estado do problema atualizado com sucesso!", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Espisódio} other {Episódios}}", + "components.IssueModal.CreateIssueModal.extras": "Extras", + "components.IssueModal.CreateIssueModal.providedetail": "Por favor, explique em detalhes o problema que você encontrou.", + "components.IssueList.IssueItem.issuestatus": "Estado", + "components.IssueList.IssueItem.issuetype": "Tipo", + "components.IssueList.IssueItem.opened": "Aberto", + "components.IssueList.IssueItem.openeduserdate": "{date} por {user}", + "components.IssueList.IssueItem.unknownissuetype": "Desconhecido", + "components.IssueDetails.toaststatusupdatefailed": "Algo deu errado ao atualizar o estado do problema.", + "components.IssueDetails.unknownissuetype": "Desconhecido", + "components.IssueList.issues": "Problemas", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Algum problema com {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Episódio Afetado", + "components.IssueModal.CreateIssueModal.problemseason": "Temporada Afetada", + "components.IssueList.IssueItem.problemepisode": "Episódio Afetado", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}", + "components.IssueList.IssueItem.viewissue": "Ver Problema", + "components.IssueList.showallissues": "Exibir Todos Problemas", + "components.IssueList.sortAdded": "Mais Recente", + "components.IssueList.sortModified": "Última Modificação", + "components.IssueModal.CreateIssueModal.allepisodes": "Todos Episódios", + "components.IssueModal.CreateIssueModal.allseasons": "Todas Temporadas", + "components.IssueModal.CreateIssueModal.episode": "Episódio {episodeNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Enviar Problema", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Relato de problema em {title} enviado com sucesso!", + "components.IssueModal.issueVideo": "Vídeo", + "components.Layout.Sidebar.issues": "Problemas", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Todas temporadas serão marcadas como disponíveis.", + "components.ManageSlideOver.downloadstatus": "Estado do Download", + "components.IssueModal.CreateIssueModal.season": "Temporada {seasonNumber}", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Algo deu errado ao enviar problema.", + "components.IssueModal.CreateIssueModal.toastviewissue": "Ver Problema", + "components.IssueModal.CreateIssueModal.reportissue": "Reportar um Problema", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Você deve prover uma descrição", + "components.IssueModal.CreateIssueModal.whatswrong": "O quê há de errado?", + "components.IssueModal.issueAudio": "Áudio", + "components.IssueModal.issueOther": "Outros", + "components.IssueModal.issueSubtitles": "Legenda", + "components.ManageSlideOver.manageModalClearMedia": "Limpar Dados de Mídia", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Isso irá remover em definitivo todos dados desse(a) {mediaType}, incluindo quaisquer solicitações para esse item. Se este item existir in sua biblioteca do Plex, os dados de mídia serão recriados na próxima sincronia.", + "components.ManageSlideOver.manageModalIssues": "Problemas Abertos", + "components.ManageSlideOver.manageModalNoRequests": "Nenhuma solicitação.", + "components.ManageSlideOver.manageModalRequests": "Solicitações", + "components.ManageSlideOver.manageModalTitle": "Gerenciar {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Marcar como Disponível em 4K", + "components.ManageSlideOver.markavailable": "Marcar como Disponível", + "components.ManageSlideOver.movie": "filme", + "components.ManageSlideOver.openarr4k": "Abrir no {arr} 4K", + "components.ManageSlideOver.tvshow": "série", + "components.PermissionEdit.createissuesDescription": "Concede permissão para reportar problemas com mídias.", + "components.NotificationTypeSelector.issuecomment": "Comentário no Problema", + "components.NotificationTypeSelector.issuecommentDescription": "Enviar notificações quando problemas receberem novos comentários.", + "components.NotificationTypeSelector.issuereopened": "Problema Re-aberto", + "components.NotificationTypeSelector.issuereopenedDescription": "Enviar notificações quando problemas são re-abertos.", + "components.NotificationTypeSelector.issueresolvedDescription": "Enviar notificações quando problemas são resolvidos.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Receber notificação quando problemas que você reportou forem re-abertos.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Receber notificação quando problemas que você reportou forem resolvidos.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Receber notificação quando outros usuários reportarem problemas.", + "components.PermissionEdit.manageissues": "Gerenciar Problemas", + "components.PermissionEdit.viewissues": "Ver Problemas", + "components.PermissionEdit.viewissuesDescription": "Concede permissão para ver problemas em mídias reportados por outros problemas.", + "components.RequestModal.requestmovies4k": "Solicitar {count} {count, plural, one {Filme} other {Filmes}} em 4K", + "components.RequestModal.selectmovies": "Selecionar Filme(s)", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Token de Acesso", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registre uma aplicação para uso com {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Chave do Usuário ou Grupo", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Seu identificador de usuário ou grupo contendo 30 caractéres", + "components.NotificationTypeSelector.issueresolved": "Problema Resolvido", + "components.ManageSlideOver.openarr": "Abrir no {arr}", + "components.NotificationTypeSelector.adminissuecommentDescription": "Receber notificação quando outros usuários comentarem nos problemas.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Receber notificação quando problemas são resolvidos por outros usuários.", + "components.NotificationTypeSelector.issuecreatedDescription": "Enviar notificações quando problemas são reportados.", + "components.NotificationTypeSelector.userissuecommentDescription": "Receber notificação quando problemas reportados por você receberem novos comentários.", + "components.MovieDetails.productioncountries": "{countryCount, plural, one {País} other {Países}} de Produção", + "components.PermissionEdit.manageissuesDescription": "Concede permissão para gerenciar problemas com mídia.", + "components.RequestModal.requestmovies": "Solicitar {count} {count, plural, one {Filme} other {Filmes}}", + "components.RequestModal.requestseasons4k": "Solicitar {seasonCount} {seasonCount, plural, one {Temporada} other {Temporadas}} em 4K", + "components.Settings.SettingsJobsCache.editJobSchedule": "Editar Tarefa", + "components.Settings.SettingsAbout.runningDevelop": "Você está usando a versão develop do Overseerr que é recomendada apenas para àqueles contribuindo com o desenvolvimento ou ajudando no teste de novas funcionalidades.", + "components.TvDetails.productioncountries": "{countryCount, plural, one {País} other {Países}} de Produção", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Token de Acesso", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Falha ao salvar configurações de notificação via Pushover.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Criar um token à partir de sua Configuração de Conta", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Configurações de notificação via Pushbullet salvas com sucesso!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Falha ao salvar configurações de notificação via Pushover.", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Você deve prover um token de acesso", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Configurações de notificação via Pushover salvas com sucesso!", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Receber notificação quando problemas forem re-abertos por outros usuários.", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Algo deu errado ao salvar tarefa.", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Você deve prover uma chave válida de acesso", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Você deve prover uma chave válida de usúario ou grupo", + "components.PermissionEdit.createissues": "Reportar Problemas", + "components.NotificationTypeSelector.issuecreated": "Problema Reportado", + "i18n.open": "Aberto", + "i18n.resolved": "Resolvido", + "components.IssueDetails.commentplaceholder": "Adicionar um comentário…", + "components.RequestModal.requestApproved": "Solicitação de {title} aprovada!", + "components.RequestModal.approve": "Aprovar Solicitação" } diff --git a/src/i18n/locale/pt_PT.json b/src/i18n/locale/pt_PT.json index 08d0aa2f0..ac7b3fab6 100644 --- a/src/i18n/locale/pt_PT.json +++ b/src/i18n/locale/pt_PT.json @@ -141,11 +141,6 @@ "components.MovieDetails.overviewunavailable": "Sinopse indisponível.", "components.MovieDetails.overview": "Sinopse", "components.MovieDetails.originallanguage": "Idioma Original", - "components.MovieDetails.manageModalTitle": "Gerir Filme", - "components.MovieDetails.manageModalRequests": "Pedidos", - "components.MovieDetails.manageModalNoRequests": "Sem pedidos.", - "components.MovieDetails.manageModalClearMediaWarning": "* Isso removerá irreversivelmente todos os dados desse filme, incluindo todos os pedidos. Se esse item existir na sua biblioteca Plex, as informações de multimédia serão recriadas durante a próxima sincronização.", - "components.MovieDetails.manageModalClearMedia": "Limpar Dados de Multimédia", "components.MovieDetails.cast": "Elenco", "components.MovieDetails.budget": "Orçamento", "components.MovieDetails.MovieCrew.fullcrew": "Equipa Técnica Completa", @@ -172,9 +167,7 @@ "components.Discover.popularmovies": "Filmes Populares", "components.Discover.discovertv": "Séries Populares", "components.Discover.discovermovies": "Filmes Populares", - "components.CollectionDetails.requestswillbecreated": "Os títulos seguintes terão pedidos criados para eles:", "components.CollectionDetails.requestcollection": "Pedir Coleção", - "components.CollectionDetails.requestSuccess": "{title} pedido com sucesso!", "components.CollectionDetails.overview": "Sinopse", "components.CollectionDetails.numberofmovies": "{count} Filmes", "pages.returnHome": "Voltar Para Página Inicial", @@ -205,7 +198,6 @@ "components.UserList.plexuser": "Utilizador Plex", "components.UserList.passwordinfodescription": "Configurar um URL de aplicação e ativar as notificações por e-mail para permitir a geração automática de palavra-passe.", "components.UserList.localuser": "Utilizador Local", - "components.UserList.lastupdated": "Atualizado", "components.UserList.importfromplexerror": "Ocorreu um erro ao importar utilizadores do Plex.", "components.UserList.importfromplex": "Importar Utilizadores do Plex", "components.UserList.importedfromplex": "{userCount, plural, one {# novo utilizador} other {# novos utilizadores}} importados do Plex com sucesso!", @@ -334,11 +326,6 @@ "components.TvDetails.overview": "Sinopse", "components.TvDetails.originallanguage": "Língua original", "components.TvDetails.network": "{networkCount, plural, one {Emissor} other {Emissores}}", - "components.TvDetails.manageModalTitle": "Gerir Série", - "components.TvDetails.manageModalRequests": "Pedidos", - "components.TvDetails.manageModalNoRequests": "Sem pedidos.", - "components.TvDetails.manageModalClearMediaWarning": "* Isso removerá irreversivelmente todos os dados dessa série, incluindo todas os pedidos. Se esse item existir na sua biblioteca Plex, as informações de multimédia serão recriadas durante a próxima sincronização.", - "components.TvDetails.manageModalClearMedia": "Limpar Dados de Multimédia", "components.TvDetails.firstAirDate": "Data da Estreia", "i18n.decline": "Rejeitar", "components.TvDetails.cast": "Elenco", @@ -401,8 +388,6 @@ "components.PermissionEdit.advancedrequest": "Pedidos Avançados", "components.PermissionEdit.adminDescription": "Acesso total de administrador. Ignora todas as outras verificações de permissão.", "components.PermissionEdit.admin": "Administrador", - "components.TvDetails.opensonarr4k": "Abrir série no Sonarr 4K", - "components.TvDetails.opensonarr": "Abrir Série no Sonarr", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Ligação Sonarr estabelecida com sucesso!", "components.Settings.SonarrModal.toastSonarrTestFailure": "Falha ao ligar ao Sonarr.", "components.Settings.SonarrModal.syncEnabled": "Ativar Sincronização", @@ -413,13 +398,6 @@ "components.TvDetails.play4konplex": "Ver em 4K no Plex", "components.MovieDetails.play4konplex": "Ver em 4K no Plex", "components.MovieDetails.playonplex": "Ver no Plex", - "components.MovieDetails.openradarr4k": "Abrir filme no Radarr 4K", - "components.MovieDetails.openradarr": "Abrir filme no Radarr", - "components.TvDetails.downloadstatus": "Estado da transferência", - "components.MovieDetails.downloadstatus": "Estado da transferência", - "components.TvDetails.markavailable": "Marcar como Disponível", - "components.TvDetails.mark4kavailable": "Marcar como Disponível em 4K", - "components.TvDetails.allseasonsmarkedavailable": "* Todas temporadas serão marcadas como disponíveis.", "components.Settings.trustProxyTip": "Permitir que o Overseerr registe corretamente os endereços IP do cliente por trás de um proxy (o Overseerr deve ser recarregado para que as alterações tenham efeito)", "components.Settings.trustProxy": "Ativar Suporte de Proxy", "components.MovieDetails.markavailable": "Marcar como Disponível", @@ -493,7 +471,6 @@ "components.Settings.SonarrModal.animelanguageprofile": "Perfil de Idioma de Anime", "components.Settings.SonarrModal.languageprofile": "Perfil de Idioma", "components.RequestModal.AdvancedRequester.languageprofile": "Perfil de Idioma", - "components.UserList.sortUpdated": "Última Atualização", "components.UserList.sortRequests": "Número de Pedidos", "components.UserList.sortDisplayName": "Nome de Exibição", "components.UserList.sortCreated": "Data de Criação", @@ -545,7 +522,6 @@ "components.UserList.userfail": "Ocorreu um erro ao gravar as permissões do utilizador.", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Definições de notificação Pushbullet gravadas com sucesso!", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Falha ao gravar as definições de notificação Pushbullet.", - "components.CollectionDetails.requestswillbecreated4k": "Os seguintes títulos terão pedidos de 4K criados para eles:", "components.CollectionDetails.requestcollection4k": "Pedir Coleção em 4K", "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filtrar conteúdo por disponibilidade da região", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Descubrir Região", @@ -621,7 +597,6 @@ "components.Settings.Notifications.pgpPassword": "Palavra-passe PGP", "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", "components.Settings.partialRequestsEnabled": "Permitir Pedidos Parciais de Séries", - "components.RequestModal.requestall": "Pedir Todas as Temporadas", "components.RequestModal.alreadyrequested": "Já Foi Pedido", "components.Discover.TvGenreSlider.tvgenres": "Géneros de Série", "components.Discover.TvGenreList.seriesgenres": "Géneros de Série", diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json index 842bb5f6b..d13e8a1c6 100644 --- a/src/i18n/locale/ru.json +++ b/src/i18n/locale/ru.json @@ -16,11 +16,6 @@ "components.Layout.UserDropdown.signout": "Выход", "components.MovieDetails.budget": "Бюджет", "components.MovieDetails.cast": "В ролях", - "components.MovieDetails.manageModalClearMedia": "Очистить данные мультимедиа", - "components.MovieDetails.manageModalClearMediaWarning": "* Это приведет к безвозвратному удалению всех данных для этого фильма, включая все запросы. Если фильм существует в вашей библиотеке Plex, мультимедийная информация о нём будет воссоздана при следующем сканировании.", - "components.MovieDetails.manageModalNoRequests": "Запросов нет.", - "components.MovieDetails.manageModalRequests": "Запросы", - "components.MovieDetails.manageModalTitle": "Управление фильмом", "components.MovieDetails.originallanguage": "Язык оригинала", "components.MovieDetails.overview": "Обзор", "components.MovieDetails.overviewunavailable": "Обзор недоступен.", @@ -31,9 +26,9 @@ "components.MovieDetails.similar": "Похожие фильмы", "components.PersonDetails.appearsin": "Появления в фильмах и сериалах", "components.PersonDetails.ascharacter": "в роли {character}", - "components.RequestBlock.seasons": "{seasonCount, plural, one {сезон} other {сезона(ов)}}", - "components.RequestCard.seasons": "{seasonCount, plural, one {сезон} other {сезона(ов)}}", - "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {сезон} other {сезона(ов)}}", + "components.RequestBlock.seasons": "{seasonCount, plural, one {Сезон} other {Сезоны}}", + "components.RequestCard.seasons": "{seasonCount, plural, one {Сезон} other {Сезоны}}", + "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Сезон} other {Сезоны}}", "components.RequestList.requests": "Запросы", "components.RequestModal.cancel": "Отменить запрос", "components.RequestModal.extras": "Дополнительно", @@ -78,7 +73,7 @@ "components.Settings.RadarrModal.toastRadarrTestFailure": "Не удалось подключиться к Radarr.", "components.Settings.RadarrModal.toastRadarrTestSuccess": "Соединение с Radarr установлено успешно!", "components.Settings.RadarrModal.validationApiKeyRequired": "Вы должны предоставить ключ API", - "components.Settings.RadarrModal.validationHostnameRequired": "Вы должны указать имя хоста или IP-адрес", + "components.Settings.RadarrModal.validationHostnameRequired": "Вы должны указать действительное имя хоста или IP-адрес", "components.Settings.RadarrModal.validationPortRequired": "Вы должны указать действительный номер порта", "components.Settings.RadarrModal.validationProfileRequired": "Вы должны выбрать профиль качества", "components.Settings.RadarrModal.validationRootFolderRequired": "Вы должны выбрать корневой каталог", @@ -99,7 +94,7 @@ "components.Settings.SonarrModal.servername": "Название сервера", "components.Settings.SonarrModal.ssl": "Использовать SSL", "components.Settings.SonarrModal.validationApiKeyRequired": "Вы должны предоставить ключ API", - "components.Settings.SonarrModal.validationHostnameRequired": "Вы должны указать имя хоста или IP-адрес", + "components.Settings.SonarrModal.validationHostnameRequired": "Вы должны указать действительное имя хоста или IP-адрес", "components.Settings.SonarrModal.validationPortRequired": "Вы должны указать действительный номер порта", "components.Settings.SonarrModal.validationProfileRequired": "Вы должны выбрать профиль качества", "components.Settings.SonarrModal.validationRootFolderRequired": "Вы должны выбрать корневой каталог", @@ -148,54 +143,46 @@ "components.Setup.signinMessage": "Начните с входа в систему с помощью учётной записи Plex", "components.Setup.welcome": "Добро пожаловать в Overseerr", "components.TvDetails.cast": "В ролях", - "components.TvDetails.manageModalClearMedia": "Очистить данные мультимедиа", - "components.TvDetails.manageModalClearMediaWarning": "* Это приведет к безвозвратному удалению всех данных для этого сериала, включая все запросы. Если сериал существует в вашей библиотеке Plex, мультимедийная информация о нём будет воссоздана при следующем сканировании.", - "components.TvDetails.manageModalNoRequests": "Запросов нет.", - "components.TvDetails.manageModalRequests": "Запросы", - "components.TvDetails.manageModalTitle": "Управление сериалом", "components.TvDetails.originallanguage": "Язык оригинала", "components.TvDetails.overview": "Обзор", "components.TvDetails.overviewunavailable": "Обзор недоступен.", "components.TvDetails.recommendations": "Рекомендации", "components.TvDetails.similar": "Похожие сериалы", "components.UserList.admin": "Администратор", - "components.UserList.created": "Создан", - "components.UserList.lastupdated": "Обновлено", + "components.UserList.created": "Присоединился", "components.UserList.plexuser": "Пользователь Plex", "components.UserList.role": "Роль", "components.UserList.totalrequests": "Запросов", "components.UserList.user": "Пользователь", "components.UserList.userlist": "Список пользователей", "i18n.approve": "Одобрить", - "i18n.approved": "Одобрено", - "i18n.available": "Доступно", + "i18n.approved": "Одобрен", + "i18n.available": "Доступен", "i18n.cancel": "Отмена", "i18n.decline": "Отклонить", - "i18n.declined": "Отклонено", + "i18n.declined": "Отклонён", "i18n.delete": "Удалить", "i18n.movies": "Фильмы", - "i18n.partiallyavailable": "Доступно частично", + "i18n.partiallyavailable": "Доступен частично", "i18n.pending": "В ожидании", "i18n.processing": "Обработка", "i18n.tvshows": "Сериалы", - "i18n.unavailable": "Недоступно", + "i18n.unavailable": "Недоступен", "pages.oops": "Упс", "pages.returnHome": "Вернуться домой", "components.CollectionDetails.overview": "Обзор", "components.CollectionDetails.numberofmovies": "{count} фильмов", "components.CollectionDetails.requestcollection": "Запросить Коллекцию", - "components.CollectionDetails.requestSuccess": "{title} успешно запрошен!", "components.Login.email": "Адрес электронной почты", "components.UserList.users": "Пользователи", "components.UserList.userdeleted": "Пользователь успешно удален!", "components.UserList.usercreatedsuccess": "Пользователь успешно создан!", "components.Settings.SettingsAbout.totalrequests": "Всего запросов", "components.UserList.sortRequests": "Количество запросов", - "components.UserList.sortCreated": "Дата создания", + "components.UserList.sortCreated": "Дата присоединения", "components.Login.password": "Пароль", "components.UserList.password": "Пароль", "components.UserList.localuser": "Локальный пользователь", - "components.UserList.sortUpdated": "Последнее обновление", "i18n.edit": "Редактировать", "components.UserList.deleteuser": "Удалить пользователя", "components.UserList.creating": "Создание…", @@ -234,13 +221,13 @@ "components.RequestModal.AdvancedRequester.default": "{name} (по умолчанию)", "components.RequestModal.AdvancedRequester.advancedoptions": "Расширенные настройки", "components.RequestList.sortModified": "Последнее изменение", - "components.RequestList.sortAdded": "Дата запроса", + "components.RequestList.sortAdded": "По дате", "components.RequestList.showallrequests": "Показать все запросы", "components.RequestButton.viewrequest": "Посмотреть запрос", "i18n.retry": "Повторить", - "i18n.requested": "Запрошено", - "components.PermissionEdit.request4k": "Запрос 4K", - "components.PermissionEdit.request": "Запрос", + "i18n.requested": "Запрошен", + "components.PermissionEdit.request4k": "Запросы 4K", + "components.PermissionEdit.request": "Запросы", "i18n.request": "Запросить", "i18n.failed": "Ошибка", "i18n.experimental": "Экспериментальный параметр", @@ -262,7 +249,7 @@ "components.Settings.SonarrModal.languageprofile": "Языковой профиль", "components.RequestModal.AdvancedRequester.languageprofile": "Языковой профиль", "components.RequestModal.AdvancedRequester.animenote": "* Этот сериал - аниме.", - "components.RequestList.RequestItem.requested": "Запрошено", + "components.RequestList.RequestItem.requested": "Запрошен", "components.RequestBlock.rootfolder": "Корневой каталог", "components.RegionSelector.regionServerDefault": "По умолчанию ({region})", "components.RegionSelector.regionDefault": "Все регионы", @@ -300,9 +287,6 @@ "components.UserList.owner": "Владелец", "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Владелец", "components.MovieDetails.markavailable": "Пометить как доступный", - "components.TvDetails.markavailable": "Пометить как доступный", - "components.MovieDetails.downloadstatus": "Статус загрузки", - "components.TvDetails.downloadstatus": "Статус загрузки", "components.StatusChacker.reloadOverseerr": "Перезагрузить", "components.StatusBadge.status4k": "4K {status}", "pages.errormessagewithcode": "{statusCode} - {error}", @@ -317,7 +301,7 @@ "components.RequestModal.AdvancedRequester.selecttags": "Выбрать теги", "components.RequestModal.AdvancedRequester.notagoptions": "Тегов нет.", "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", - "components.RequestList.RequestItem.requesteddate": "Запрошено", + "components.RequestList.RequestItem.requesteddate": "Запрошен", "components.RequestList.RequestItem.modifieduserdate": "{date} пользователем {user}", "components.RequestList.RequestItem.modified": "Изменено", "components.RequestList.RequestItem.editrequest": "Редактировать запрос", @@ -415,8 +399,6 @@ "components.MovieDetails.showless": "Свернуть", "components.MovieDetails.playonplex": "Воспроизвести в Plex", "components.MovieDetails.play4konplex": "Воспроизвести в Plex в 4К", - "components.MovieDetails.openradarr4k": "Открыть фильм в 4К Radarr", - "components.MovieDetails.openradarr": "Открыть фильм в Radarr", "components.MovieDetails.mark4kavailable": "Пометить как доступный в 4К", "components.MovieDetails.MovieCast.fullcast": "Полный актёрский состав", "components.Login.validationpasswordrequired": "Вы должны предоставить пароль", @@ -436,11 +418,9 @@ "components.Discover.DiscoverTvLanguage.languageSeries": "Сериалы на языке \"{language}\"", "components.Discover.DiscoverTvGenre.genreSeries": "Сериалы в жанре \"{genre}\"", "components.Discover.DiscoverNetwork.networkSeries": "Сериалы {network}", - "components.CollectionDetails.requestswillbecreated4k": "Будут созданы 4К запросы на следующие названия:", - "components.CollectionDetails.requestswillbecreated": "Будут созданы запросы на следующие названия:", "components.CollectionDetails.requestcollection4k": "Запросить Коллекцию в 4К", - "components.QuotaSelector.movies": "{count, plural, one {фильм} other {фильма(ов)}}", - "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {фильм} other {фильма(ов)}}", + "components.QuotaSelector.movies": "{count, plural, one {фильм} other {фильмы}}", + "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {фильм} other {фильмы}}", "components.RequestModal.QuotaDisplay.allowedRequestsUser": "Этому пользователю разрешено запрашивать {limit} {type} каждые {days} дней.", "components.RequestModal.QuotaDisplay.allowedRequests": "Вам разрешено запрашивать {limit} {type} каждые {days} дней.", "components.Settings.SonarrModal.testFirstRootFolders": "Протестировать соединение для загрузки корневых каталогов", @@ -449,9 +429,9 @@ "components.Settings.RadarrModal.testFirstRootFolders": "Протестировать подключение для загрузки корневых каталогов", "components.Settings.RadarrModal.loadingrootfolders": "Загрузка корневых каталогов…", "components.RequestModal.AdvancedRequester.destinationserver": "Сервер-получатель", - "components.RequestList.RequestItem.mediaerror": "Соответствующее название для этого запроса больше недоступно.", + "components.RequestList.RequestItem.mediaerror": "Название, связанное с этим запросом, больше недоступно.", "components.RequestList.RequestItem.failedretry": "Что-то пошло не так при попытке повторить запрос.", - "components.RequestCard.mediaerror": "Соответствующее название для этого запроса больше недоступно.", + "components.RequestCard.mediaerror": "Название, связанное с этим запросом, больше недоступно.", "components.RequestCard.failedretry": "Что-то пошло не так при попытке повторить запрос.", "components.RequestButton.viewrequest4k": "Посмотреть 4К запрос", "components.RequestButton.requestmore4k": "Запросить больше в 4К", @@ -466,27 +446,27 @@ "components.RequestButton.approverequest": "Одобрить запрос", "components.RequestBlock.server": "Сервер-получатель", "components.QuotaSelector.tvRequests": "{quotaLimit} {сезонов} за {quotaDays} {дней}", - "components.QuotaSelector.seasons": "{count, plural, one {сезон} other {сезона(ов)}}", + "components.QuotaSelector.seasons": "{count, plural, one {сезон} other {сезоны}}", "components.RequestBlock.requestoverrides": "Переопределение запроса", "components.QuotaSelector.unlimited": "Неограниченно", "components.QuotaSelector.movieRequests": "{quotaLimit} {фильмов} за {quotaDays} {дней}", "components.QuotaSelector.days": "{count, plural, one {день} other {дней}}", "components.PlexLoginButton.signingin": "Выполняется вход…", "components.PersonDetails.birthdate": "Рожден(а) {birthdate}", - "components.PermissionEdit.viewrequestsDescription": "Предоставить разрешение на просмотр запросов других пользователей.", - "components.PermissionEdit.usersDescription": "Предоставить разрешение на управление пользователями Overseerr. Пользователи с этим разрешением не могут предоставлять права администратора и редактировать пользователей, являющихся администраторами.", - "components.PermissionEdit.settingsDescription": "Предоставить разрешение на изменение настроек Overseerr. Пользователь должен иметь это разрешение, чтобы предоставить его другим.", - "components.PermissionEdit.requestTvDescription": "Предоставить разрешение на запрос всех сериалов, отличных от 4К.", - "components.PermissionEdit.requestTv": "Запрос сериалов", - "components.PermissionEdit.requestMoviesDescription": "Предоставить разрешение на запрос всех фильмов, отличных от 4К.", - "components.PermissionEdit.requestMovies": "Запрос фильмов", - "components.PermissionEdit.requestDescription": "Предоставить разрешение на запрос всех медиафайлов, отличных от 4К.", - "components.PermissionEdit.request4kTvDescription": "Предоставить разрешение на запрос 4К сериалов.", - "components.PermissionEdit.request4kTv": "Запрос 4К сериалов", - "components.PermissionEdit.request4kMoviesDescription": "Предоставить разрешение на запрос 4К фильмов.", - "components.PermissionEdit.request4kMovies": "Запрос 4К фильмов", - "components.PermissionEdit.request4kDescription": "Предоставить разрешение на запрос 4К медиафайлов.", - "components.PermissionEdit.managerequestsDescription": "Предоставить разрешение на управление запросами Overseerr. Все запросы пользователя, имеющего данное разрешение, будут одобряться автоматически.", + "components.PermissionEdit.viewrequestsDescription": "Предоставить разрешение на просмотр медиа-запросов, отправленных другими пользователями.", + "components.PermissionEdit.usersDescription": "Предоставить разрешение на управление пользователями. Пользователи с этим разрешением не могут предоставлять права администратора и редактировать пользователей, являющихся администраторами.", + "components.PermissionEdit.settingsDescription": "Предоставить разрешение на изменение глобальных настроек. Пользователь должен иметь это разрешение, чтобы предоставить его другим.", + "components.PermissionEdit.requestTvDescription": "Предоставить разрешение на отправку запросов всех сериалов, отличных от 4К.", + "components.PermissionEdit.requestTv": "Запросы сериалов", + "components.PermissionEdit.requestMoviesDescription": "Предоставить разрешение на отправку запросов всех фильмов, отличных от 4К.", + "components.PermissionEdit.requestMovies": "Запросы фильмов", + "components.PermissionEdit.requestDescription": "Предоставить разрешение на отправку запросов всех медиафайлов, отличных от 4К.", + "components.PermissionEdit.request4kTvDescription": "Предоставить разрешение на отправку запросов сериалов в 4К.", + "components.PermissionEdit.request4kTv": "Запросы сериалов в 4К", + "components.PermissionEdit.request4kMoviesDescription": "Предоставить разрешение на отправку запросов фильмов в 4К.", + "components.PermissionEdit.request4kMovies": "Запросы фильмов в 4К", + "components.PermissionEdit.request4kDescription": "Предоставить разрешение на отправку запросов медиафайлов в 4К.", + "components.PermissionEdit.managerequestsDescription": "Предоставить разрешение на управление медиа-запросами. Все запросы пользователя, имеющего данное разрешение, будут одобряться автоматически.", "components.PermissionEdit.autoapproveSeriesDescription": "Предоставить разрешение на автоматическое одобрение всех сериалов, отличных от 4К.", "components.PermissionEdit.autoapproveSeries": "Автоматическое одобрение сериалов", "components.PermissionEdit.autoapprove4kMoviesDescription": "Предоставить разрешение на автоматическое одобрение 4К фильмов.", @@ -495,11 +475,11 @@ "components.PermissionEdit.autoapprove4kSeriesDescription": "Предоставить разрешение на автоматическое одобрение 4К сериалов.", "components.PermissionEdit.autoapproveMoviesDescription": "Предоставить разрешение на автоматическое одобрение всех фильмов, отличных от 4К.", "components.PermissionEdit.autoapproveMovies": "Автоматическое одобрение фильмов", - "components.PermissionEdit.autoapprove4kDescription": "Предоставить разрешение на автоматическое одобрение всех 4К запросов.", - "components.PermissionEdit.autoapproveDescription": "Предоставить разрешение на автоматическое одобрение всех запросов, отличных от 4К.", + "components.PermissionEdit.autoapprove4kDescription": "Предоставить разрешение на автоматическое одобрение всех 4К медиа-запросов.", + "components.PermissionEdit.autoapproveDescription": "Предоставить разрешение на автоматическое одобрение всех медиа-запросов, отличных от 4К.", "components.PermissionEdit.autoapprove4k": "Автоматическое одобрение 4К", "components.PermissionEdit.autoapprove": "Автоматическое одобрение", - "components.PermissionEdit.advancedrequestDescription": "Предоставить разрешение на использование дополнительных параметров запроса.", + "components.PermissionEdit.advancedrequestDescription": "Предоставить разрешение на изменение дополнительных параметров запроса.", "components.PermissionEdit.advancedrequest": "Расширенные запросы", "components.PermissionEdit.adminDescription": "Администратор имеет полный доступ. Игнорирует все другие настройки разрешений.", "components.NotificationTypeSelector.usermediarequestedDescription": "Получать уведомления, когда другие пользователи отправляют новые медиа-запросы, требующие одобрения.", @@ -551,8 +531,8 @@ "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Настройки уведомлений Pushover успешно сохранены!", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Не удалось сохранить настройки уведомлений Pushover.", "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Зарегистрируйте приложение для использования с Overseerr", - "i18n.view": "Вид", - "i18n.notrequested": "Не запрошено", + "i18n.view": "Посмотреть", + "i18n.notrequested": "Не запрошен", "i18n.noresults": "Результатов нет.", "i18n.delimitedlist": "{a}, {b}", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "URL веб-перехватчика", @@ -613,10 +593,8 @@ "components.Settings.partialRequestsEnabled": "Разрешить частичные запросы сериалов", "components.Settings.mediaTypeSeries": "сериал", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Регион для поиска фильмов и сериалов", - "components.TvDetails.allseasonsmarkedavailable": "* Все сезоны будут помечены как доступные.", - "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {сезон} other {сезона(ов)}}", + "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {сезон} other {сезоны}}", "components.RequestModal.QuotaDisplay.season": "сезон", - "components.RequestModal.requestall": "Запросить все сезоны", "components.RequestModal.pendingapproval": "Ваш запрос ожидает одобрения.", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Настройки уведомлений по электронной почте успешно сохранены!", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Не удалось сохранить настройки уведомлений по электронной почте.", @@ -825,10 +803,7 @@ "components.UserList.accounttype": "Тип", "components.TvDetails.playonplex": "Воспроизвести в Plex", "components.TvDetails.play4konplex": "Воспроизвести в Plex в 4К", - "components.TvDetails.opensonarr4k": "Открыть сериал в 4К Sonarr", - "components.TvDetails.opensonarr": "Открыть сериал в Sonarr", "components.TvDetails.nextAirDate": "Следующая дата выхода в эфир", - "components.TvDetails.mark4kavailable": "Пометить как доступный в 4К", "components.TvDetails.firstAirDate": "Дата первого эфира", "components.TvDetails.episodeRuntimeMinutes": "{runtime} минут", "components.TvDetails.episodeRuntime": "Продолжительность эпизода", @@ -861,7 +836,7 @@ "components.Settings.general": "Общее", "components.Settings.hideAvailable": "Скрывать доступные медиа", "components.Settings.SettingsUsers.userSettingsDescription": "Настройте глобальные параметры и параметры по умолчанию для пользователей.", - "components.Settings.email": "Адрес электронной почты", + "components.Settings.email": "Электронная почта", "components.Settings.csrfProtectionHoverTip": "НЕ включайте этот параметр, если вы не понимаете, что делаете!", "components.Settings.csrfProtection": "Включить защиту от CSRF", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL-адрес не должен заканчиваться косой чертой", @@ -888,5 +863,128 @@ "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Что-то пошло не так при сохранении задания.", "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Каждый {jobScheduleHours, plural, one {час} other {{jobScheduleHours} часа(ов)}}", "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Каждую {jobScheduleMinutes, plural, one {минуту} other {{jobScheduleMinutes} минут(ы)}}", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.areyousuredelete": "Вы уверены, что хотите удалить этот комментарий?", + "components.IssueDetails.IssueComment.delete": "Удалить комментарий", + "components.IssueDetails.IssueComment.edit": "Редактировать комментарий", + "components.IssueDetails.IssueComment.postedby": "Опубликовано {relativeTime} пользователем {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Опубликовано {relativeTime} пользователем {username} (изменено)", + "components.IssueDetails.IssueComment.validationComment": "Вы должны ввести сообщение", + "components.IssueDetails.IssueDescription.deleteissue": "Удалить проблему", + "components.IssueDetails.IssueDescription.description": "Описание", + "components.IssueDetails.IssueDescription.edit": "Редактировать описание", + "components.IssueDetails.allseasons": "Все сезоны", + "components.IssueDetails.allepisodes": "Все эпизоды", + "components.ManageSlideOver.manageModalClearMedia": "Очистить данные мультимедиа", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Это приведёт к необратимому удалению всех данных для этого {mediaType}, включая любые запросы. Если этот элемент существует в вашей библиотеке Plex, мультимедийная информация о нём будет воссоздана во время следующего сканирования.", + "components.IssueDetails.problemepisode": "Затронутый эпизод", + "components.ManageSlideOver.manageModalRequests": "Запросы", + "components.IssueDetails.closeissue": "Закрыть проблему", + "components.IssueDetails.closeissueandcomment": "Закрыть с комментарием", + "components.IssueDetails.comments": "Комментарии", + "components.IssueDetails.deleteissueconfirm": "Вы уверены, что хотите удалить эту проблему?", + "components.IssueDetails.episode": "Эпизод {episodeNumber}", + "components.IssueDetails.lastupdated": "Последнее обновление", + "components.IssueDetails.openinarr": "Открыть в {arr}", + "components.IssueDetails.toasteditdescriptionfailed": "Что-то пошло не так при редактировании описания проблемы.", + "components.IssueDetails.toastissuedeletefailed": "Что-то пошло не так при удалении проблемы.", + "components.IssueDetails.toaststatusupdatefailed": "Что-то пошло не так при обновлении статуса проблемы.", + "components.IssueDetails.unknownissuetype": "Неизвестный", + "components.IssueModal.CreateIssueModal.episode": "Эпизод {episodeNumber}", + "components.ManageSlideOver.mark4kavailable": "Пометить как доступный в 4К", + "components.IssueModal.CreateIssueModal.extras": "Дополнительно", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Есть проблема с {title}?", + "components.IssueModal.CreateIssueModal.problemseason": "Затронутый сезон", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Все сезоны будут помечены как доступные.", + "components.ManageSlideOver.downloadstatus": "Статус загрузки", + "components.ManageSlideOver.manageModalIssues": "Открытые проблемы", + "components.ManageSlideOver.manageModalNoRequests": "Запросов нет.", + "components.ManageSlideOver.manageModalTitle": "Управлять {mediaType}", + "components.ManageSlideOver.markavailable": "Пометить как доступный", + "components.ManageSlideOver.movie": "фильм", + "components.ManageSlideOver.openarr": "Открыть в {arr}", + "components.ManageSlideOver.openarr4k": "Открыть в 4К {arr}", + "components.ManageSlideOver.tvshow": "сериал", + "components.NotificationTypeSelector.userissueresolvedDescription": "Получать уведомления, когда проблемы, о которых вы сообщили, получают решение.", + "components.NotificationTypeSelector.issuecomment": "Комментарий к проблеме", + "components.PermissionEdit.createissuesDescription": "Предоставить разрешение на сообщения о проблемах с медиафайлами.", + "components.PermissionEdit.manageissuesDescription": "Предоставить разрешение на управление проблемами с медиафайлами.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Получать уведомления, когда другие пользователи сообщают о проблемах.", + "components.PermissionEdit.createissues": "Сообщения о проблемах", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Настройки уведомлений Pushover успешно сохранены!", + "components.PermissionEdit.viewissues": "Просмотр проблем", + "components.PermissionEdit.viewissuesDescription": "Предоставить разрешение на просмотр проблем с медиафайлами, о которых сообщили другие пользователи.", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Вы должны предоставить действительный ключ пользователя или группы", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Токен доступа", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Токен API приложения", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Создайте токен на странице настроек вашей учётной записи", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Вы должны предоставить действительный токен приложения", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Ваш 30-значный идентификатор пользователя или группы", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Не удалось сохранить настройки уведомлений Pushbullet.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Настройки уведомлений Pushbullet успешно сохранены!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Зарегистрируйте приложение для использования с {applicationTitle}", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Ключ пользователя или группы", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Не удалось сохранить настройки уведомлений Pushover.", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Вы должны предоставить токен доступа", + "i18n.resolved": "Решён", + "components.IssueDetails.openedby": "#{issueId} открыта {relativeTime} пользователем {username}", + "components.IssueDetails.openin4karr": "Открыть в 4К {arr}", + "components.IssueDetails.play4konplex": "Воспроизвести в Plex в 4К", + "components.IssueDetails.problemseason": "Затронутый сезон", + "components.IssueDetails.reopenissue": "Снова открыть проблему", + "components.IssueDetails.season": "Сезон {seasonNumber}", + "components.IssueDetails.toasteditdescriptionsuccess": "Описание проблемы успешно отредактировано!", + "components.IssueDetails.toastissuedeleted": "Проблема успешно удалена!", + "components.IssueDetails.toaststatusupdated": "Статус проблемы успешно обновлен!", + "components.IssueList.IssueItem.issuetype": "Тип", + "components.IssueModal.CreateIssueModal.allseasons": "Все сезоны", + "components.IssueModal.CreateIssueModal.problemepisode": "Затронутый эпизод", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Что-то пошло не так при отправке проблемы.", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Отчёт о проблеме для {title} успешно отправлен!", + "components.IssueModal.CreateIssueModal.toastviewissue": "Просмотреть проблему", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Вы должны предоставить описание", + "components.IssueModal.issueAudio": "Аудио", + "components.IssueModal.issueOther": "Другое", + "components.IssueModal.issueSubtitles": "Субтитры", + "components.IssueModal.issueVideo": "Видео", + "components.IssueModal.CreateIssueModal.reportissue": "Сообщить о проблеме", + "components.NotificationTypeSelector.adminissuecommentDescription": "Получать уведомления, когда другие пользователи остовляют комментарии к проблемам.", + "components.NotificationTypeSelector.userissuecommentDescription": "Получать уведомления, когда к проблемам, о которых вы сообщили, появляются новые комментарии.", + "components.NotificationTypeSelector.issuecommentDescription": "Отправлять уведомления, когда к проблемам появляются новые комментарии.", + "components.NotificationTypeSelector.issuecreated": "Проблема опубликована", + "components.NotificationTypeSelector.issueresolved": "Проблема решена", + "components.NotificationTypeSelector.issueresolvedDescription": "Отправлять уведомления, когда проблемы получают решение.", + "components.NotificationTypeSelector.issuecreatedDescription": "Отправлять уведомления, когда появляются сообщения о проблемах.", + "components.PermissionEdit.manageissues": "Управление проблемами", + "i18n.open": "Открыть", + "components.IssueList.IssueItem.problemepisode": "Затронутый эпизод", + "components.IssueList.IssueItem.unknownissuetype": "Неизвестный", + "components.IssueList.issues": "Проблемы", + "components.IssueList.IssueItem.opened": "Открыта", + "components.IssueDetails.nocomments": "Комментариев нет.", + "components.IssueDetails.issuepagetitle": "Проблема", + "components.IssueDetails.deleteissue": "Удалить проблему", + "components.IssueDetails.issuetype": "Тип", + "components.IssueDetails.leavecomment": "Комментарий", + "components.IssueDetails.playonplex": "Воспроизвести в Plex", + "components.IssueDetails.reopenissueandcomment": "Снова открыть с комментарием", + "components.IssueList.IssueItem.issuestatus": "Статус", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Сезон} other {Сезоны}}", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Эпизод} other {Эпизоды}}", + "components.IssueList.IssueItem.viewissue": "Просмотреть проблему", + "components.IssueList.showallissues": "Показать все проблемы", + "components.IssueList.sortModified": "По дате изменения", + "components.IssueModal.CreateIssueModal.allepisodes": "Все эпизоды", + "components.IssueList.IssueItem.openeduserdate": "{date} пользователем {user}", + "components.IssueList.sortAdded": "По дате добавления", + "components.IssueModal.CreateIssueModal.season": "Сезон {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Отправить проблему", + "components.IssueModal.CreateIssueModal.providedetail": "Пожалуйста, предоставьте подробное описание проблемы, с которой вы столкнулись.", + "components.IssueModal.CreateIssueModal.whatswrong": "Что не так?", + "components.Layout.Sidebar.issues": "Проблемы", + "components.NotificationTypeSelector.issuereopenedDescription": "Отправлять уведомления, когда проблемы открыты заново.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Получать уведомления, когда проблемы о которых вы сообщили будут открыты заново.", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Получать уведомления, когда проблемы открыты заново другими пользователями.", + "components.NotificationTypeSelector.issuereopened": "Проблема открыта заново", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Получать уведомления, когда проблемы решены другими пользователями." } diff --git a/src/i18n/locale/sr.json b/src/i18n/locale/sr.json index b919c907c..e017d0ed4 100644 --- a/src/i18n/locale/sr.json +++ b/src/i18n/locale/sr.json @@ -1,7 +1,6 @@ { - "components.TvDetails.manageModalClearMedia": "Obriši sve infomacije o fajlu", "components.RequestModal.requestseasons": "Zatraži {seasonCount} {seasonCount, plural, jednu {Season} više {Seasons}}", - "components.Layout.Sidebar.dashboard": "Pronadji Novo", + "components.Layout.Sidebar.dashboard": "Pronađi novo", "pages.returnHome": "Povratak na glavnu stranicu", "pages.oops": "Ups", "i18n.unavailable": "Nije dostupno", @@ -25,7 +24,6 @@ "components.UserList.totalrequests": "Ukupno Zahteva", "components.UserList.role": "Uloga", "components.UserList.plexuser": "Plex Korisnik", - "components.UserList.lastupdated": "Zadnja Promena", "components.UserList.deleteuser": "Izbriši Korisnika", "components.UserList.deleteconfirm": "Da li ste sigurni da želite da izbrišete ovog korisnika? Svi njegovi zahtevi će biti izbrisani.", "components.UserList.created": "Napravljeno", @@ -37,10 +35,6 @@ "components.TvDetails.overview": "Pregled", "components.TvDetails.originallanguage": "Originalni Jezik", "components.TvDetails.network": "Mreža", - "components.TvDetails.manageModalTitle": "Upravljaj Serijama", - "components.TvDetails.manageModalRequests": "Zahtevi", - "components.TvDetails.manageModalNoRequests": "Nema Zahteva", - "components.TvDetails.manageModalClearMediaWarning": "Ovo će ukloniti sve podatke o mediji uključujući i zahteve za ovaj sadrzaj nepovratno. Ako ovaj sadržaj postoji u vašoj plex biblioteci infomacija o mediji će biti kreirana pri sledećoj sinhronizaciji.", "components.TvDetails.cast": "Repertoar", "components.TvDetails.anime": "Anime", "components.TvDetails.TvCast.fullseriescast": "Ceo Repertoar Serije", @@ -178,7 +172,7 @@ "components.RequestModal.season": "Sezona", "components.RequestModal.requesttitle": "Zatraži {title}", "components.RequestModal.requestfrom": "Trenutno postoji zahtev na čekanju od {username}", - "components.RequestModal.requestadmin": "Vaš zahtev će odmah biti prihvaćen.", + "components.RequestModal.requestadmin": "Ovaj zahtev će odmah biti prihvaćen.", "components.RequestModal.requestSuccess": "poslat zahtev za {title} .", "components.RequestModal.requestCancel": "Zahtev za {title} otkazan.", "components.RequestModal.pendingrequest": "Zahtev za {title} na čekanju", @@ -200,11 +194,6 @@ "components.MovieDetails.overviewunavailable": "Pregled nije dostupan.", "components.MovieDetails.overview": "Pregled", "components.MovieDetails.originallanguage": "Originalni Jezik", - "components.MovieDetails.manageModalTitle": "Upravljaj Filmom", - "components.MovieDetails.manageModalRequests": "Zahtevi", - "components.MovieDetails.manageModalNoRequests": "Nema zahteva", - "components.MovieDetails.manageModalClearMediaWarning": "Ovo će ukloniti sve media podatke uključujuci i sve zahteve za ovu stvar nepovratno. Ako ova stvar postoji u Vašoj Plex biblioteci, informacija o mediji će biti kreirana pri sledećoj sinhronizaciji.", - "components.MovieDetails.manageModalClearMedia": "Očisti sve media podatke", "components.MovieDetails.cast": "Postava", "components.MovieDetails.budget": "Budžet", "components.MovieDetails.MovieCast.fullcast": "Kompletna Glumačka Postava", @@ -223,5 +212,6 @@ "components.Discover.discovertv": "Popularne Serije", "components.Discover.discovermovies": "Popunarni Filmov", "pages.errormessagewithcode": "{statusCode} - {error}", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.UserProfile.movierequests": "Zahtev za film" } diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json index a4d8015d4..90c5b5aba 100644 --- a/src/i18n/locale/sv.json +++ b/src/i18n/locale/sv.json @@ -4,7 +4,7 @@ "components.Setup.continue": "Fortsätt", "components.Setup.configureservices": "Konfigurera Tjänster", "components.Setup.configureplex": "Konfigurera Plex", - "components.Settings.validationPortRequired": "Du måste ange ett gilltigt port nummer", + "components.Settings.validationPortRequired": "Du måste ange ett giltigt port nummer", "components.Settings.validationHostnameRequired": "Du måste ange giltigt värdnamn eller IP-adress", "components.Settings.toastSettingsSuccess": "Inställningar sparade!", "components.Settings.toastSettingsFailure": "Något gick fel när inställningarna skulle sparas.", @@ -50,7 +50,7 @@ "components.Settings.SonarrModal.validationProfileRequired": "Du måste ange en kvalitetsprofil", "components.Settings.SonarrModal.validationPortRequired": "Du måste ange ett giltigt port nummer", "components.Settings.SonarrModal.validationNameRequired": "Du måste ange ett servernamn", - "components.Settings.SonarrModal.validationHostnameRequired": "Du måste ange värdnamn eller IP-adress", + "components.Settings.SonarrModal.validationHostnameRequired": "Du måste ange giltigt värdnamn eller IP-adress", "components.Settings.SonarrModal.validationApiKeyRequired": "Du måste ange en API-nyckel", "components.Settings.SonarrModal.testFirstRootFolders": "Testa anslutningen för att ladda in root-mapparna", "components.Settings.SonarrModal.testFirstQualityProfiles": "Testa anslutningen för att ladda in kvalitetsprofilerna", @@ -85,7 +85,7 @@ "components.Settings.RadarrModal.validationPortRequired": "Du måste ange en giltig port nummer", "components.Settings.RadarrModal.validationNameRequired": "Du måste ange ett servernamn", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Du måste välja minsta tillgänglighet", - "components.Settings.RadarrModal.validationHostnameRequired": "Du måste ange hostname eller IP-adress", + "components.Settings.RadarrModal.validationHostnameRequired": "Du måste ange giltigt hostname eller IP-adress", "components.Settings.RadarrModal.validationApiKeyRequired": "Du måste ange en API-nyckel", "components.Settings.RadarrModal.toastRadarrTestSuccess": "Anslutningen till Radarr lyckades!", "components.Settings.RadarrModal.toastRadarrTestFailure": "Misslyckades att ansluta till Radarr.", @@ -151,11 +151,6 @@ "components.MovieDetails.overview": "Beskrivning", "components.MovieDetails.overviewunavailable": "Beskrivning otillgänglig.", "components.MovieDetails.originallanguage": "Ursprungligt Språk", - "components.MovieDetails.manageModalTitle": "Hantera Film", - "components.MovieDetails.manageModalRequests": "Förfrågningar", - "components.MovieDetails.manageModalNoRequests": "Inga förfrågningar.", - "components.MovieDetails.manageModalClearMediaWarning": "Denna handling kan ej ångras och kommer att radera all data för denna film, inklusive alla förfrågningar. Om det här objektet finns i ditt Plex-bibliotek kommer media information att återskapas vid nästa inläsning.", - "components.MovieDetails.manageModalClearMedia": "Rensa mediadata", "components.MovieDetails.cast": "Roller", "components.MovieDetails.budget": "Budget", "components.MovieDetails.MovieCast.fullcast": "Rollista", @@ -203,10 +198,9 @@ "components.UserList.totalrequests": "Förfrågningar", "components.UserList.role": "Roll", "components.UserList.plexuser": "Plex", - "components.UserList.lastupdated": "Uppdaterad", "components.UserList.deleteuser": "Ta bort användare", "components.UserList.deleteconfirm": "Är du säker på att du vill ta bort den här användaren? Alla förfrågningsdata tas bort permanent.", - "components.UserList.created": "Skapad", + "components.UserList.created": "Anslöt sig", "components.UserList.admin": "Administratör", "components.TvDetails.similar": "Liknande TV-serier", "components.TvDetails.showtype": "Serietyp", @@ -215,11 +209,6 @@ "components.TvDetails.overview": "Beskrivning", "components.TvDetails.originallanguage": "Ursprungligt Språk", "components.TvDetails.network": "{networkCount, plural, one {Nätverk} other {Nätverk}}", - "components.TvDetails.manageModalTitle": "Hantera TV-serier", - "components.TvDetails.manageModalRequests": "Förfrågningar", - "components.TvDetails.manageModalNoRequests": "Inga förfrågningar.", - "components.TvDetails.manageModalClearMediaWarning": "* Denna handling går inte att ångra och raderar all media data för denna serie, inklusive förfrågningar. Om objektet finns i ditt Plexbibliotek kommer mediainformationen att återskapas vid nästa skanning.", - "components.TvDetails.manageModalClearMedia": "Rensa mediadata", "components.TvDetails.cast": "Roller", "i18n.close": "Stäng", "components.Setup.loginwithplex": "Logga in med Plex", @@ -285,7 +274,7 @@ "components.RequestModal.request4ktitle": "Begär {titel} i 4K", "components.RequestModal.pending4krequest": "Väntande 4K-förfrågan för {title]", "components.RequestList.sortModified": "Senast ändrad", - "components.RequestList.sortAdded": "Datum för begäran", + "components.RequestList.sortAdded": "Senaste", "components.RequestList.showallrequests": "Visa alla förfrågningar", "components.RequestList.RequestItem.failedretry": "Något gick fel vid nytt försök av begäran.", "components.RequestButton.viewrequest4k": "Visa 4K-begäran", @@ -310,24 +299,22 @@ "components.NotificationTypeSelector.mediaapproved": "Media Godkänt", "components.MovieDetails.viewfullcrew": "Visa Filmteam", "components.MovieDetails.MovieCrew.fullcrew": "Filmteam", - "components.CollectionDetails.requestswillbecreated": "Följande titlar kommer begäras:", - "components.CollectionDetails.requestSuccess": "{title} har begärts!", "components.Settings.csrfProtection": "Aktivera CSRF-skydd", "components.UserList.userssaved": "Användarbehörigheter sparade!", "components.UserList.bulkedit": "Mass-redigering", - "components.PermissionEdit.usersDescription": "Bevilja behörighet att hantera Overseerr-användare. Användare med denna behörighet kan inte ändra användare eller bevilja administratörsbehörighet.", + "components.PermissionEdit.usersDescription": "Bevilja behörighet att hantera användare. Användare med denna behörighet kan inte ändra användare med eller bevilja administratörsbehörighet.", "components.PermissionEdit.users": "Hantera Användare", - "components.PermissionEdit.settingsDescription": "Bevilja behörighet att modifiera Overseerr-inställningar. En användare måste ha denna behörighet för att kunna ge den till andra.", + "components.PermissionEdit.settingsDescription": "Ge tillstånd att ändra globala inställningar. En användare måste ha denna behörighet för att ge den till andra.", "components.PermissionEdit.settings": "Hantera Inställningar", - "components.PermissionEdit.requestDescription": "Bevilja behörighet att begära media som inte är 4K.", - "components.PermissionEdit.request4kTvDescription": "Bevilja behörighet att begära 4K serier.", + "components.PermissionEdit.requestDescription": "Ge tillstånd att skicka förfrågningar för icke-4K-media.", + "components.PermissionEdit.request4kTvDescription": "Bevilja tillstånd att skicka förfrågningar för 4K-serien.", "components.PermissionEdit.request4kTv": "Begära 4K Serier", - "components.PermissionEdit.request4kMoviesDescription": "Bevilja behörighet att begära 4K filmer.", + "components.PermissionEdit.request4kMoviesDescription": "Ge tillstånd att skicka in förfrågningar om 4K-filmer.", "components.PermissionEdit.request4kMovies": "Begära 4K Filmer", - "components.PermissionEdit.request4kDescription": "Bevilja behörighet att begära 4K media.", + "components.PermissionEdit.request4kDescription": "Bevilja behörighet att skicka förfrågningar om 4K-media.", "components.PermissionEdit.request4k": "Begära 4K", "components.PermissionEdit.request": "Begära", - "components.PermissionEdit.managerequestsDescription": "Bevilja behörighet att hantera Overseerr-förfrågningar. Alla förfrågningar som görs av en användare med den här behörigheten kommer att godkännas.", + "components.PermissionEdit.managerequestsDescription": "Bevilja behörighet att hantera medieförfrågningar. Alla förfrågningar som görs av en användare med den här behörigheten kommer att godkännas.", "components.PermissionEdit.managerequests": "Hantera Förfrågningar", "components.PermissionEdit.adminDescription": "Fullständig administratörsbehörighet. Överskrider alla andra behörighetskontroller.", "components.PlexLoginButton.signinwithplex": "Logga in", @@ -370,20 +357,17 @@ "components.PermissionEdit.autoapproveSeries": "Auto-Godkänn Serier", "components.PermissionEdit.autoapproveMoviesDescription": "Bevilja automatiskt godkännande för icke-4K-filmförfrågningar.", "components.PermissionEdit.autoapproveMovies": "Auto-Godkänn Filmer", - "components.PermissionEdit.autoapproveDescription": "Bevilja automatiskt godkännande för alla icke-4K-förfrågningar.", + "components.PermissionEdit.autoapproveDescription": "Bevilja automatiskt godkännande för alla icke-4K-medieförfrågningar.", "components.PermissionEdit.autoapprove": "Auto-Godkänn", - "components.PermissionEdit.advancedrequestDescription": "Ge behörighet att använda avancerade inställningar vid en begäran.", + "components.PermissionEdit.advancedrequestDescription": "Ge behörighet att redigera avancerade inställningar vid en begäran.", "components.PermissionEdit.advancedrequest": "Avancerade Förfrågningar", "components.PermissionEdit.admin": "Admin", "components.NotificationTypeSelector.mediadeclinedDescription": "Skicka meddelanden när medieförfrågningar avvisas.", "components.NotificationTypeSelector.mediadeclined": "Media Avböjd", "components.MovieDetails.play4konplex": "Spela upp i 4K på Plex", "components.MovieDetails.playonplex": "Spela upp på Plex", - "components.MovieDetails.openradarr4k": "Öppna Filmen i 4K Radarr", - "components.MovieDetails.openradarr": "Öppna Filmen i Radarr", "components.MovieDetails.markavailable": "Markera som Tillgänglig", "components.MovieDetails.mark4kavailable": "Markera som tillgänglig i 4K", - "components.MovieDetails.downloadstatus": "Nedladdningsstatus", "components.Login.validationpasswordrequired": "Du måste ange ett lösenord", "components.Login.signinwithplex": "Använd ditt Plex-konto", "components.Login.signinwithoverseerr": "Använd ditt {applicationTitle}-konto", @@ -422,7 +406,7 @@ "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL:n får inte avslutas med ett slash", "components.Settings.SonarrModal.validationApplicationUrl": "Du måste ange en giltig URL", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL:n får inte avslutas med ett slash", - "components.PermissionEdit.viewrequestsDescription": "Bevilja behörighet att visa andra användares förfrågningar.", + "components.PermissionEdit.viewrequestsDescription": "Ge tillstånd att se medieförfrågningar som skickats av andra användare.", "components.PermissionEdit.viewrequests": "Visa Förfrågningar", "components.RequestModal.AdvancedRequester.requestas": "Begär Som", "components.Setup.setup": "Installationsguide", @@ -455,14 +439,8 @@ "components.Settings.SettingsJobsCache.cache": "Cache", "components.Settings.trustProxyTip": "Tillåt Overseerr att korrekt registrera klienters IP-adresser bakom en proxy (Overseerr måste laddas om för att ändringarna skall gå i kraft)", "components.Settings.trustProxy": "Aktivera Proxy-stöd", - "components.TvDetails.markavailable": "Markera som Tillgänglig", - "components.TvDetails.mark4kavailable": "Markera som tillgängligt i 4K", - "components.TvDetails.allseasonsmarkedavailable": "* Alla säsongen kommer att bli markerade som tillgängliga.", "components.TvDetails.playonplex": "Spela upp på Plex", "components.TvDetails.play4konplex": "Spela upp i 4K på Plex", - "components.TvDetails.opensonarr4k": "Öppna Serien i 4K Sonarr", - "components.TvDetails.opensonarr": "Öppna Serien i Sonarr", - "components.TvDetails.downloadstatus": "Nedladdningsstatus", "components.Settings.SonarrModal.syncEnabled": "Aktivera skanning", "components.Settings.SonarrModal.externalUrl": "Extern URL", "components.Settings.RadarrModal.syncEnabled": "Aktivera skanning", @@ -533,10 +511,9 @@ "components.UserProfile.ProfileHeader.profile": "Visa profil", "components.UserProfile.ProfileHeader.joindate": "Medlem sedan {joindate}", "components.UserList.userfail": "Något gick fel när användarbehörigheter sparades.", - "components.UserList.sortUpdated": "Senast uppdaterad", "components.UserList.sortRequests": "Antal förfrågningar", "components.UserList.sortDisplayName": "Visningsnamn", - "components.UserList.sortCreated": "Skapad", + "components.UserList.sortCreated": "Datum för anslutning", "components.UserList.owner": "Ägare", "components.UserList.edituser": "Redigera användarebehörigheter", "components.UserList.accounttype": "Typ", @@ -593,7 +570,7 @@ "components.PermissionEdit.autoapprove4kSeries": "Godkänn automatiskt 4K-serier", "components.PermissionEdit.autoapprove4kMoviesDescription": "Bevilja automatiskt godkännande för 4K-filmförfrågningar.", "components.PermissionEdit.autoapprove4kMovies": "Godkänn automatiskt 4K-filmer", - "components.PermissionEdit.autoapprove4kDescription": "Bevilja automatiskt godkännande för alla 4K-förfrågningar.", + "components.PermissionEdit.autoapprove4kDescription": "Bevilja automatiskt godkännande för alla 4K-medieförfrågningar.", "components.PermissionEdit.autoapprove4k": "Automatiskt godkännande av 4K", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Skicka meddelanden när användare skickar in nya medieförfrågningar som godkänns automatiskt.", "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatiskt Godkänd", @@ -608,7 +585,6 @@ "components.Discover.DiscoverNetwork.networkSeries": "{network} Serier", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Filmer", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Filmer", - "components.CollectionDetails.requestswillbecreated4k": "Följande titlar kommer begäras i 4K:", "components.CollectionDetails.requestcollection4k": "Begär Kollektion i 4K", "components.UserProfile.UserSettings.unauthorizedDescription": "Du har inte behörighet att ändra den här användarens inställningar.", "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Du kan inte ändra dina egna behörigheter.", @@ -625,7 +601,6 @@ "components.Settings.Notifications.pgpPrivateKey": "PGP Privat nyckel", "components.Settings.Notifications.pgpPasswordTip": "Signera krypterade e-postmeddelanden med OpenPGP ", "components.Settings.Notifications.pgpPassword": "PGP Lösenord", - "components.RequestModal.requestall": "Begär alla säsonger", "components.RequestModal.alreadyrequested": "Redan begärd", "components.UserProfile.UserSettings.UserGeneralSettings.general": "Allmänna", "pages.somethingwentwrong": "Något gick fel", @@ -765,7 +740,7 @@ "components.Layout.VersionStatus.outofdate": "Föråldrad", "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {ändring} other {ändringar}} efter", "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Ditt konto har för närvarande inget lösenord. Konfigurera ett lösenord nedan för att aktivera inloggning som en \"lokal användare\" med din e-postadress.", - "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Det här användarkontot har för närvarande inget lösenord. Konfigurera ett lösenord nedan så att det här kontot kan logga in som en \"lokal användare\".", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Det här användarkontot har för närvarande inget lösenord. Konfigurera ett lösenord nedan så att det här kontot kan logga in som en \"lokal användare.\"", "components.Settings.serviceSettingsDescription": "Konfigurera din server {serverType} nedan. Du kan ansluta flera {serverType} servrar, men bara två av dem kan markeras som standard (en icke-4K och en 4K). Administratörer kan åsidosätta servern som används för att behandla nya förfrågningar innan godkännande.", "components.Settings.noDefaultServer": "Minst en {serverType} server måste markeras som standard för att {mediaType} -förfrågningar ska kunna behandlas.", "components.Settings.noDefaultNon4kServer": "Om du bara har en enda {serverType} server för både icke-4K- och 4K-innehåll (eller om du bara laddar ner 4K-innehåll) bör din {serverType} server INTE betecknas som en 4K-server.", @@ -791,7 +766,7 @@ "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "Du måste ange en giltig URL", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea-aviseringsinställningar har sparats!", "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea-aviseringsinställningarna kunde inte sparas.", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Krävs endast om du inte använder standardprofilen", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Krävs endast om du inte använder default", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Profilnamn", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Aktivera agent", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Skickar Pushbullet testmeddelande …", @@ -827,9 +802,9 @@ "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet testmeddelande skickat!", "components.Settings.SettingsUsers.newPlexLoginTip": "Tillåt Plex-användare att logga in utan att först importeras", "components.Settings.SettingsUsers.newPlexLogin": "Aktivera ny Plex-inloggning", - "components.PermissionEdit.requestTvDescription": "Bevilja tillstånd att begära serier som inte är 4K.", + "components.PermissionEdit.requestTvDescription": "Ge tillstånd att skicka förfrågningar för icke-4K-serier.", "components.PermissionEdit.requestTv": "Begär serie", - "components.PermissionEdit.requestMoviesDescription": "Ger tillstånd att begära filmer som inte är 4K.", + "components.PermissionEdit.requestMoviesDescription": "Ge tillåtelse att skicka in förfrågningar om icke-4K-filmer.", "components.PermissionEdit.requestMovies": "Begär filmer", "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Standard ({language})", "components.Settings.locale": "Visningsspråk", @@ -888,5 +863,128 @@ "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Något gick fel vid sparning av jobbet.", "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Jobbet har redigerats!", "components.Settings.SettingsAbout.runningDevelop": "Du använder develop grenen av Overseerr, som endast rekommenderas för dem som bidrar till utvecklingen eller hjälper till med tester i den absoluta framkanten.", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.areyousuredelete": "Är du säker på att du vill ta bort denna kommentar?", + "components.IssueDetails.IssueComment.delete": "Ta bort kommentar", + "components.IssueDetails.IssueComment.edit": "Redigera kommentar", + "components.IssueDetails.IssueComment.postedby": "Skriven {relativeTime} av {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Skriven {relativeTime} av {username} (Redigerad)", + "components.IssueDetails.IssueComment.validationComment": "Du måste ange ett meddelande", + "components.IssueDetails.IssueDescription.deleteissue": "Ta bort problem", + "components.IssueDetails.allepisodes": "Alla avsnitt", + "components.IssueDetails.closeissue": "Stäng problem", + "components.IssueDetails.closeissueandcomment": "Stäng med kommentar", + "components.IssueDetails.IssueDescription.description": "Beskrivning", + "components.IssueDetails.IssueDescription.edit": "Redigera beskrivning", + "components.IssueDetails.allseasons": "Alla säsonger", + "components.IssueDetails.comments": "Kommentarer", + "components.IssueDetails.episode": "Avsnitt {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Problem", + "components.IssueDetails.issuetype": "Typ", + "components.IssueDetails.lastupdated": "Senast uppdaterad", + "components.IssueDetails.openinarr": "Öppna i {arr}", + "components.IssueDetails.problemepisode": "Påverkat avsnitt", + "components.IssueDetails.toasteditdescriptionfailed": "Något gick fel när problembeskrivningen redigerades.", + "components.IssueDetails.toastissuedeletefailed": "Något gick fel när problemet skulle tas bort.", + "components.IssueDetails.toaststatusupdatefailed": "Något gick fel när problemets status uppdaterades.", + "components.IssueDetails.unknownissuetype": "Okänd", + "components.IssueDetails.openedby": "#{issueId} öppnades {relativeTime} av {username}", + "components.IssueDetails.openin4karr": "Öppna i 4K {arr}", + "components.IssueDetails.play4konplex": "Spela upp i 4K på Plex", + "components.IssueDetails.playonplex": "Spela på Plex", + "components.IssueDetails.problemseason": "Påverkad säsong", + "components.IssueDetails.reopenissue": "Återöppna problemet", + "components.IssueDetails.reopenissueandcomment": "Återöppna med en kommentar", + "components.IssueDetails.season": "Säsong {seasonNumber}", + "components.IssueDetails.toasteditdescriptionsuccess": "Problembeskrivningen har redigerats!", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Säsong} other {Säsonger}}", + "components.IssueList.IssueItem.unknownissuetype": "Okänd", + "components.IssueList.IssueItem.openeduserdate": "{date} av {user}", + "components.IssueList.IssueItem.viewissue": "Visa problem", + "components.IssueList.showallissues": "Visa alla problem", + "components.IssueDetails.deleteissue": "Ta bort problem", + "components.IssueDetails.deleteissueconfirm": "Är du säker att du vill ta bort detta problem?", + "components.IssueList.IssueItem.opened": "Öppnad", + "components.IssueDetails.leavecomment": "Kommentar", + "components.IssueList.issues": "Problem", + "components.IssueDetails.nocomments": "Inga kommentarer.", + "components.IssueDetails.toaststatusupdated": "Problemstatus har uppdaterats!", + "components.IssueDetails.toastissuedeleted": "Problemet har tagits bort!", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Avsnitt} other {Avsnitt}}", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.issuetype": "Typ", + "components.IssueList.IssueItem.problemepisode": "Påverkat avsnitt", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Problemrapport för {title} har skickats in!", + "components.ManageSlideOver.tvshow": "serie", + "components.ManageSlideOver.openarr4k": "Öppna i 4K {arr}", + "components.IssueModal.CreateIssueModal.reportissue": "Rapportera ett problem", + "components.IssueModal.CreateIssueModal.season": "Säsong {seasonNumber}", + "components.IssueModal.issueOther": "Övrigt", + "components.PermissionEdit.manageissuesDescription": "Bevilja behörighet att hantera medieproblem.", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Något gick fel när problemet skickades in.", + "components.IssueModal.CreateIssueModal.submitissue": "Skicka in problemet", + "components.ManageSlideOver.movie": "film", + "components.IssueModal.CreateIssueModal.whatswrong": "Vad är fel?", + "components.IssueModal.issueAudio": "Ljud", + "components.Layout.Sidebar.issues": "Problem", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Du måste ange en beskrivning", + "components.IssueModal.issueSubtitles": "Undertext", + "components.IssueModal.issueVideo": "Video", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Användar- eller gruppnyckel", + "components.ManageSlideOver.openarr": "Öppna i {arr}", + "components.ManageSlideOver.manageModalTitle": "Hantera {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Markera som tillgängligt i 4K", + "components.NotificationTypeSelector.adminissuecommentDescription": "Få aviseringar när ett problem får nya kommentarer.", + "components.ManageSlideOver.manageModalRequests": "Förfrågningar", + "components.PermissionEdit.manageissues": "Hantera problem", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Inställningarna för pushover-meddelanden kunde inte sparas.", + "components.ManageSlideOver.markavailable": "Markera som tillgänglig", + "components.NotificationTypeSelector.issuecomment": "Problemkommentar", + "components.NotificationTypeSelector.issuecommentDescription": "Skicka aviseringar när problem får nya kommentarer.", + "components.PermissionEdit.createissuesDescription": "Bevilja behörighet att rapportera medieproblem.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Din 30-teckens användar- eller gruppidentifierare", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registrera en applikation för användning med {applicationTitle}", + "components.PermissionEdit.viewissuesDescription": "Ge tillstånd att se medieproblem som rapporterats av andra användare.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Api-token för program", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Inställningar för pushover-meddelanden har sparats!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Du måste tillhandahålla en giltig applikationstoken", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Du måste ange en giltig användar- eller gruppnyckel", + "components.IssueModal.CreateIssueModal.problemseason": "Påverkad säsong", + "components.IssueModal.CreateIssueModal.toastviewissue": "Visa problem", + "components.NotificationTypeSelector.issuecreated": "Problem rappoterat", + "components.PermissionEdit.createissues": "Rapportera problem", + "components.PermissionEdit.viewissues": "Visa problem", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Detta tar bort all data för denna {mediaType}, inklusive eventuella begäranden, på ett oåterkalleligt sätt. Om det här objektet finns i ditt Plex-bibliotek kommer medieinformationen att återskapas vid nästa genomsökning.", + "components.ManageSlideOver.manageModalNoRequests": "Inga förfrågningar.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Få meddelande när dina rapporterade problem har blivit lösta.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Åtkomsttoken", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Du måste tillhandahålla en åtkomsttoken", + "components.IssueList.sortAdded": "Senaste", + "components.IssueList.sortModified": "Senast ändrad", + "components.IssueModal.CreateIssueModal.allepisodes": "Alla avsnitt", + "components.IssueModal.CreateIssueModal.allseasons": "Alla säsonger", + "components.IssueModal.CreateIssueModal.episode": "Avsnitt {episodeNumber}", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Är det något problem med {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Påverkat avsnitt", + "components.IssueModal.CreateIssueModal.providedetail": "Ge en detaljerad förklaring av det problem du stötte på.", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Alla säsonger kommer bli markerade som tillgängliga.", + "components.ManageSlideOver.downloadstatus": "Nerladdningsstatus", + "components.ManageSlideOver.manageModalClearMedia": "Rensa mediadata", + "components.NotificationTypeSelector.issuecreatedDescription": "Skicka aviseringar när problem rapporteras.", + "components.NotificationTypeSelector.issueresolved": "Problem löst", + "components.NotificationTypeSelector.issueresolvedDescription": "Skicka aviseringar när problem blir lösta.", + "components.NotificationTypeSelector.userissuecommentDescription": "Få meddelanden när ett problem som du har mottagit har får nya kommentarer.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Få meddelanden när andra användare rapporterar problem.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Skapa en token från dina kontoinställningar", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Inställningar för Pushbullet-aviseringar kunde inte sparas.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Inställningar för Pushbullet-aviseringar har sparats!", + "i18n.open": "Öppna", + "i18n.resolved": "Löst", + "components.ManageSlideOver.manageModalIssues": "Öppna problem", + "components.IssueModal.CreateIssueModal.extras": "Extras", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Få avisering när en användare återöppnar ett problem.", + "components.NotificationTypeSelector.issuereopened": "Problem återöppnat", + "components.NotificationTypeSelector.issuereopenedDescription": "Skicka avisering när ett problem at återöppnats.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Få avisering när ett problem som du har rapporterat har återöppnats.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Få avisering när ett problem har blivit löst." } diff --git a/src/i18n/locale/zh_Hans.json b/src/i18n/locale/zh_Hans.json index a8b956c13..b0d13134a 100644 --- a/src/i18n/locale/zh_Hans.json +++ b/src/i18n/locale/zh_Hans.json @@ -12,10 +12,9 @@ "components.UserList.usercreatedfailed": "建立新用户中出了点问题。", "components.UserList.user": "用户", "components.UserList.totalrequests": "请求数", - "components.UserList.sortUpdated": "最后更新日期", "components.UserList.sortRequests": "请求数", "components.UserList.sortDisplayName": "显示名称", - "components.UserList.sortCreated": "建立日期", + "components.UserList.sortCreated": "加入日期", "components.UserList.role": "角色", "components.UserList.plexuser": "Plex 用户", "components.UserList.passwordinfodescription": "设置应用程序网址以及启用电子邮件通知,才能自动生成密码。", @@ -24,7 +23,6 @@ "components.UserList.nouserstoimport": "没有未导入的 Plex 用户。", "components.UserList.localuser": "本地用户", "components.UserList.localLoginDisabled": "允许本地登录的设置目前被禁用。", - "components.UserList.lastupdated": "更新日期", "components.UserList.importfromplexerror": "从 Plex 导入用户中出了点问题。", "components.UserList.importfromplex": "从 Plex 导入用户", "components.UserList.importedfromplex": "导入 {userCount} 个 Plex 用户成功!", @@ -35,7 +33,7 @@ "components.UserList.deleteconfirm": "确定要刪除这个用户吗?此用户的所有储存资料将被清除。", "components.UserList.creating": "创建中…", "components.UserList.createlocaluser": "建立本地用户", - "components.UserList.created": "建立日期", + "components.UserList.created": "加入", "components.UserList.create": "建立", "components.UserList.bulkedit": "批量编辑", "components.UserList.autogeneratepasswordTip": "通過电子邮件发送服务器生成的密码给用户", @@ -54,30 +52,19 @@ "components.TvDetails.overview": "简介", "components.TvDetails.originaltitle": "原始標題", "components.TvDetails.originallanguage": "原始语言", - "components.TvDetails.opensonarr4k": "开启 4K Sonarr 服务器", - "components.TvDetails.opensonarr": "开启 Sonarr 服务器", "components.TvDetails.nextAirDate": "下一次播出日期", "components.TvDetails.network": "电视网", - "components.TvDetails.markavailable": "標记为可观看", - "components.TvDetails.mark4kavailable": "標记 4K 版为可观看", - "components.TvDetails.manageModalTitle": "电视节目管理", - "components.TvDetails.manageModalRequests": "请求", - "components.TvDetails.manageModalNoRequests": "没有请求。", - "components.TvDetails.manageModalClearMediaWarning": "*这电视节目的所有储存资料将被永久刪除(包括用户提交的请求)。如果节目存在于您的 Plex 服务器,资料会在媒体库扫描时重新建立。", - "components.TvDetails.manageModalClearMedia": "清除储存资料", "components.TvDetails.firstAirDate": "原始播出日期", "components.TvDetails.episodeRuntimeMinutes": "{runtime} 分钟", "components.TvDetails.episodeRuntime": "劇集片長", - "components.TvDetails.downloadstatus": "下载状态", "components.TvDetails.cast": "演员阵容", "components.TvDetails.anime": "动漫", - "components.TvDetails.allseasonsmarkedavailable": "*每季将被標记为可观看。", "components.TvDetails.TvCrew.fullseriescrew": "制作群", "components.TvDetails.TvCast.fullseriescast": "演员阵容", "components.StatusChacker.reloadOverseerr": "刷新页面", "components.StatusChacker.newversionavailable": "软件更新", "components.StatusChacker.newversionDescription": "Overseerr 软件已更新。请点击以下的按钮刷新页面。", - "components.StatusBadge.status4k": "4K 版 {status}", + "components.StatusBadge.status4k": "4K 版{status}", "components.Setup.welcome": "欢迎來到 Overseerr", "components.Setup.tip": "提示", "components.Setup.signinMessage": "首先,请使用您的 Plex 账户登入", @@ -190,7 +177,7 @@ "components.Settings.SonarrModal.validationPortRequired": "请输入有效的端口", "components.Settings.SonarrModal.validationNameRequired": "请输入服务器名称", "components.Settings.SonarrModal.validationLanguageProfileRequired": "必须设置语言", - "components.Settings.SonarrModal.validationHostnameRequired": "请输入有效的主机名称或 IP 地址", + "components.Settings.SonarrModal.validationHostnameRequired": "您必须提供有效的主机名或 IP 地址", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "必须刪除結尾斜線", "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "必须添加前置斜線", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "必须刪除結尾斜線", @@ -219,15 +206,15 @@ "components.PermissionEdit.autoapproveSeries": "电视节目自动批准", "components.PermissionEdit.autoapproveMoviesDescription": "自动批准非 4K 电影请求。", "components.PermissionEdit.autoapproveMovies": "电影自动批准", - "components.PermissionEdit.autoapproveDescription": "自动批准所有非 4K 请求。", + "components.PermissionEdit.autoapproveDescription": "自动批准所有非 4K 媒体请求。", "components.PermissionEdit.autoapprove4kSeriesDescription": "自动批准 4K 电视节目请求。", "components.PermissionEdit.autoapprove4kSeries": "4K 电视节目自动批准", "components.PermissionEdit.autoapprove4kMoviesDescription": "自动批准 4K 电影请求。", "components.PermissionEdit.autoapprove4kMovies": "4K 电影自动批准", - "components.PermissionEdit.autoapprove4kDescription": "自动批准所有 4K 请求。", + "components.PermissionEdit.autoapprove4kDescription": "自动批准所有 4K 媒体请求。", "components.PermissionEdit.autoapprove4k": "自动批准 4K", "components.PermissionEdit.autoapprove": "自动批准", - "components.PermissionEdit.advancedrequestDescription": "授予使用进阶请求选项的权限。", + "components.PermissionEdit.advancedrequestDescription": "授予修改高级媒体请求选项的权限。", "components.PermissionEdit.advancedrequest": "进阶请求", "components.PermissionEdit.adminDescription": "授予最高权限;旁路所有权限检查。", "components.PermissionEdit.admin": "管理员", @@ -266,16 +253,8 @@ "components.MovieDetails.overview": "简介", "components.MovieDetails.originaltitle": "原始標題", "components.MovieDetails.originallanguage": "原始语言", - "components.MovieDetails.openradarr4k": "开启 4K Radarr 服务器", - "components.MovieDetails.openradarr": "开启 Radarr 服务器", "components.MovieDetails.markavailable": "標记为可观看", "components.MovieDetails.mark4kavailable": "標记 4K 版为可观看", - "components.MovieDetails.manageModalTitle": "电影管理", - "components.MovieDetails.manageModalRequests": "请求", - "components.MovieDetails.manageModalNoRequests": "没有请求。", - "components.MovieDetails.manageModalClearMediaWarning": "*这电影的所有储存资料将被永久刪除(包括用户提交的请求)。如果电影存在于您的 Plex 服务器,资料会在媒体库扫描时重新建立。", - "components.MovieDetails.manageModalClearMedia": "清除储存资料", - "components.MovieDetails.downloadstatus": "下载状态", "components.MovieDetails.cast": "演员阵容", "components.MovieDetails.budget": "电影成本", "components.MovieDetails.MovieCrew.fullcrew": "制作群", @@ -303,7 +282,7 @@ "components.Layout.Sidebar.settings": "设定", "components.Layout.Sidebar.requests": "请求", "components.Layout.Sidebar.dashboard": "探索", - "components.Layout.SearchInput.searchPlaceholder": "搜索电影、电视节目、人物…", + "components.Layout.SearchInput.searchPlaceholder": "搜索电影、电视节目", "components.Layout.LanguagePicker.displaylanguage": "显示语言", "components.LanguageSelector.originalLanguageDefault": "所有语言", "components.LanguageSelector.languageServerDefault": "默认设置({language})", @@ -332,11 +311,8 @@ "components.Discover.DiscoverNetwork.networkSeries": "{network} 电视节目", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language}电影", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre}电影", - "components.CollectionDetails.requestswillbecreated4k": "为以下的电影提交 4K 请求:", - "components.CollectionDetails.requestswillbecreated": "为以下的电影提交请求:", "components.CollectionDetails.requestcollection4k": "提交 4K 系列请求", "components.CollectionDetails.requestcollection": "提交系列请求", - "components.CollectionDetails.requestSuccess": "为 {title} 提交请求成功!", "components.CollectionDetails.overview": "简介", "components.CollectionDetails.numberofmovies": "{count} 部电影", "components.AppDataWarning.dockerVolumeMissingDescription": "必须使用繫結掛载(bind mount)指定某个宿主机器的资料夹跟容器內的 {appDataPath} 资料夹連通,才能保存 Overseerr 的配置和数据。", @@ -554,7 +530,7 @@ "components.Settings.SettingsJobsCache.cacheksize": "键储存大小", "components.Settings.SettingsJobsCache.cachekeys": "键数", "components.Settings.SettingsJobsCache.cachehits": "击中数", - "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} 缓存清除成功!", + "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} 缓存已清除。", "components.Settings.SettingsJobsCache.cacheDescription": "外部应用程序介面(external API)请求将存到缓存,以減少 API 呼叫次数。", "components.Settings.SettingsJobsCache.cache": "缓存", "components.Settings.SettingsAbout.version": "软件版本", @@ -584,7 +560,7 @@ "components.Settings.RadarrModal.validationPortRequired": "请输入有效的端口", "components.Settings.RadarrModal.validationNameRequired": "请输入服务器名称", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "必须设置最低状态", - "components.Settings.RadarrModal.validationHostnameRequired": "请输入有效的主机名称或 IP 地址", + "components.Settings.RadarrModal.validationHostnameRequired": "您必须提供有效的主机名或 IP 地址", "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "必须刪除結尾斜線", "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "必须添加前置斜線", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "必须刪除結尾斜線", @@ -734,7 +710,6 @@ "components.RequestModal.requesterror": "提交请求中出了点问题。", "components.RequestModal.requestedited": "{title} 的请求编辑成功!", "components.RequestModal.requestcancelled": "{title} 的请求已被取消。", - "components.RequestModal.requestall": "提交请求", "components.RequestModal.requestadmin": "此请求将自动被批准。", "components.RequestModal.requestSuccess": "为 {title} 提交请求成功!", "components.RequestModal.requestCancel": "{title} 的请求已被取消。", @@ -776,7 +751,7 @@ "components.RequestModal.AdvancedRequester.animenote": "*这是个动漫节目。", "components.RequestModal.AdvancedRequester.advancedoptions": "进阶选项", "components.RequestList.sortModified": "最后修改时间", - "components.RequestList.sortAdded": "请求时间", + "components.RequestList.sortAdded": "最新", "components.RequestList.showallrequests": "查看所有请求", "components.RequestList.requests": "请求", "components.RequestList.RequestItem.seasons": "季数", @@ -825,25 +800,25 @@ "components.PersonDetails.birthdate": "{birthdate}-", "components.PersonDetails.appearsin": "演出", "components.PersonDetails.alsoknownas": "別名:{names}", - "components.PermissionEdit.viewrequestsDescription": "授予查看其他用户的请求的权限。", + "components.PermissionEdit.viewrequestsDescription": "授予查看其他用户提交的媒体请求的权限。", "components.PermissionEdit.viewrequests": "查看请求", - "components.PermissionEdit.usersDescription": "授予管理用户的权限。不包括编辑管理员用户或授予管理员的权限。", + "components.PermissionEdit.usersDescription": "授予管理用户的权限。 拥有此权限的用户无法修改具有管理员权限的用户或授予管理员权限。", "components.PermissionEdit.users": "用户管理", - "components.PermissionEdit.settingsDescription": "授予管理 Overseerr 设置的权限。", + "components.PermissionEdit.settingsDescription": "授予修改全局设置的权限。 用户必须具有此权限才能将其授予其他人。", "components.PermissionEdit.settings": "设置管理", - "components.PermissionEdit.requestTvDescription": "授予提交非 4K 电视节目请求的权限。", + "components.PermissionEdit.requestTvDescription": "授予提交非 4K 电视剧请求的权限。", "components.PermissionEdit.requestTv": "提交电视节目请求", "components.PermissionEdit.requestMoviesDescription": "授予提交非 4K 电影请求的权限。", "components.PermissionEdit.requestMovies": "提交电影请求", - "components.PermissionEdit.requestDescription": "授予提交非 4K 请求的权限。", - "components.PermissionEdit.request4kTvDescription": "授予提交 4K 电视节目请求的权限。", + "components.PermissionEdit.requestDescription": "授予提交非 4K 媒体请求的权限。", + "components.PermissionEdit.request4kTvDescription": "授予提交 4K 电视剧请求的权限。", "components.PermissionEdit.request4kTv": "提交 4K 电视节目请求", - "components.PermissionEdit.request4kMoviesDescription": "授予为电影提交 4K 请求的权限。", + "components.PermissionEdit.request4kMoviesDescription": "授予提交 4K 影片请求的权限。", "components.PermissionEdit.request4kMovies": "提交 4K 电影请求", - "components.PermissionEdit.request4kDescription": "授予提交 4K 请求的权限。", + "components.PermissionEdit.request4kDescription": "授予提交 4K 媒体请求的权限。", "components.PermissionEdit.request4k": "提交 4K 请求", "components.PermissionEdit.request": "提交请求", - "components.PermissionEdit.managerequestsDescription": "授予管理请求的权限,以及所有自动批准的权限。", + "components.PermissionEdit.managerequestsDescription": "授予管理媒体请求的权限。 拥有此权限的用户提出的所有请求都将被自动批准。", "components.PermissionEdit.managerequests": "请求管理", "components.Settings.Notifications.emailsettingssaved": "电子邮件通知设置保存成功!", "components.Settings.Notifications.emailsettingsfailed": "电子邮件通知设置保存失败。", @@ -879,5 +854,45 @@ "components.Settings.Notifications.encryptionImplicitTls": "使用传输层安全标准(TLS)", "components.Settings.Notifications.encryptionDefault": "盡可能使用 STARTTLS", "components.Settings.Notifications.encryption": "加密方式", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.IssueComment.areyousuredelete": "您确定删除此条评论吗?", + "components.IssueDetails.IssueComment.delete": "删除评论", + "components.IssueDetails.IssueComment.edit": "编辑评论", + "components.IssueDetails.IssueComment.postedby": "由 {username} 发布于 {relativeTime}", + "components.IssueDetails.IssueComment.postedbyedited": "由 {username} 发布于 {relativeTime}(已编辑)", + "components.IssueDetails.IssueComment.validationComment": "您必须输入一条消息", + "components.IssueDetails.IssueDescription.deleteissue": "删除 Issue", + "components.IssueDetails.allseasons": "所有季数", + "components.IssueDetails.nocomments": "没有评论。", + "components.IssueDetails.openedby": "#{issueId} 由 {username} 打开于 {relativeTime}", + "components.IssueDetails.toaststatusupdatefailed": "更新 issue 状态时出错。", + "components.IssueDetails.unknownissuetype": "未知", + "components.IssueDetails.IssueDescription.description": "描述", + "components.IssueDetails.IssueDescription.edit": "编辑描述", + "components.IssueDetails.closeissue": "关闭 Issue", + "components.IssueDetails.closeissueandcomment": "评论后关闭", + "components.IssueDetails.comments": "评论", + "components.IssueDetails.deleteissueconfirm": "您是否确实要删除此 issue?", + "components.IssueDetails.episode": "第 {episodeNumber} 集", + "components.IssueDetails.issuepagetitle": "问题", + "components.IssueDetails.lastupdated": "最后更新时间", + "components.IssueDetails.leavecomment": "评论", + "components.IssueDetails.openinarr": "在 {arr} 中打开", + "components.IssueDetails.problemseason": "有问题的季数", + "components.IssueDetails.toasteditdescriptionfailed": "编辑 issue 描述时出错。", + "components.IssueDetails.toastissuedeletefailed": "删除 issue 时出错。", + "components.IssueDetails.play4konplex": "在 Plex 中播放 4K", + "components.IssueDetails.openin4karr": "在 4K {arr} 中打开", + "components.IssueDetails.playonplex": "在 Plex 上播放", + "components.IssueDetails.problemepisode": "有问题的集数", + "components.IssueDetails.toasteditdescriptionsuccess": "Issue 描述编辑成功!", + "components.IssueDetails.toaststatusupdated": "Issue 状态更新成功!", + "components.IssueDetails.reopenissue": "重新打开 Issue", + "components.IssueDetails.allepisodes": "所有剧集", + "components.IssueDetails.issuetype": "类型", + "components.IssueDetails.deleteissue": "删除 Issue", + "components.IssueDetails.reopenissueandcomment": "评论后重新打开", + "components.IssueDetails.season": "第 {seasonNumber} 季", + "components.IssueDetails.toastissuedeleted": "Issue 删除成功!", + "components.IssueModal.CreateIssueModal.episode": "第 {episodeNumber} 集" } diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json index ad1d2784c..ddf084f0c 100644 --- a/src/i18n/locale/zh_Hant.json +++ b/src/i18n/locale/zh_Hant.json @@ -6,7 +6,7 @@ "components.Settings.SonarrModal.apiKey": "應用程式密鑰", "components.Settings.apikey": "應用程式密鑰", "components.Settings.RadarrModal.apiKey": "應用程式密鑰", - "components.Settings.Notifications.senderName": "發件人姓名", + "components.Settings.Notifications.senderName": "發件人名稱", "components.Settings.Notifications.emailsender": "發件人電子郵件地址", "components.Settings.Notifications.authUser": "SMTP 使用者", "components.Settings.Notifications.authPass": "SMTP 密碼", @@ -21,7 +21,7 @@ "i18n.partiallyavailable": "部分可觀看", "i18n.unavailable": "不可觀看", "components.StatusChacker.newversionavailable": "軟體更新", - "components.StatusBadge.status4k": "4K 版 {status}", + "components.StatusBadge.status4k": "4K 版{status}", "components.Setup.tip": "提示", "components.Setup.welcome": "歡迎來到 Overseerr", "components.TvDetails.TvCast.fullseriescast": "演員陣容", @@ -51,7 +51,7 @@ "components.Settings.sonarrsettings": "Sonarr 設定", "components.Settings.radarrsettings": "Radarr 設定", "components.Settings.menuPlexSettings": "Plex", - "components.UserList.importfromplexerror": "從 Plex 匯入使用者中出了點問題。", + "components.UserList.importfromplexerror": "匯入 Plex 使用者中出了點問題。", "components.UserList.importfromplex": "從 Plex 匯入使用者", "components.UserList.importedfromplex": "匯入 {userCount} 個 Plex 使用者成功!", "components.UserList.localuser": "本地使用者", @@ -60,11 +60,6 @@ "components.UserList.autogeneratepassword": "自動生成密碼", "i18n.tvshows": "電視節目", "pages.oops": "哎呀", - "components.TvDetails.manageModalTitle": "電視節目管理", - "components.MovieDetails.manageModalNoRequests": "沒有請求。", - "components.TvDetails.manageModalRequests": "請求", - "components.TvDetails.manageModalNoRequests": "沒有請求。", - "components.TvDetails.manageModalClearMedia": "清除儲存資料", "components.TvDetails.firstAirDate": "原始播出日期", "i18n.delete": "刪除", "i18n.declined": "已拒絕", @@ -82,14 +77,14 @@ "components.Settings.SonarrModal.loadingprofiles": "載入中…", "components.Settings.RadarrModal.loadingrootfolders": "載入中…", "components.Settings.RadarrModal.loadingprofiles": "載入中…", - "components.Settings.toastSettingsSuccess": "設定保存成功!", + "components.Settings.toastSettingsSuccess": "設定儲存成功!", "components.Search.searchresults": "搜尋結果", "components.RequestModal.seasonnumber": "第 {number} 季", "components.RequestModal.season": "季數", "components.RequestModal.numberofepisodes": "集數", "components.RequestModal.cancel": "取消請求", - "components.RequestList.sortModified": "最後修改時間", - "components.RequestList.sortAdded": "請求時間", + "components.RequestList.sortModified": "最後修改", + "components.RequestList.sortAdded": "最新", "components.RequestList.showallrequests": "查看所有請求", "components.RequestList.requests": "請求", "components.RequestList.RequestItem.seasons": "季數", @@ -126,9 +121,6 @@ "components.Layout.Sidebar.dashboard": "探索", "components.MovieDetails.overview": "概要", "components.MovieDetails.originallanguage": "原始語言", - "components.MovieDetails.manageModalTitle": "電影管理", - "components.MovieDetails.manageModalRequests": "請求", - "components.MovieDetails.manageModalClearMedia": "清除儲存資料", "components.MovieDetails.budget": "電影成本", "components.MovieDetails.MovieCrew.fullcrew": "製作群", "components.MovieDetails.MovieCast.fullcast": "演員陣容", @@ -152,9 +144,7 @@ "components.Discover.popularmovies": "熱門電影", "components.Discover.discovertv": "熱門電視節目", "components.Discover.discovermovies": "熱門電影", - "components.CollectionDetails.requestswillbecreated": "為以下的電影提出請求:", "components.CollectionDetails.requestcollection": "提出系列請求", - "components.CollectionDetails.requestSuccess": "為 {title} 提出請求成功!", "components.CollectionDetails.overview": "概要", "components.UserList.userdeleteerror": "刪除使用者中出了點問題。", "components.UserList.userdeleted": "使用者刪除成功!", @@ -163,7 +153,6 @@ "components.UserList.user": "使用者", "components.UserList.totalrequests": "請求數", "components.UserList.plexuser": "Plex 使用者", - "components.UserList.lastupdated": "更新日期", "components.UserList.email": "電子郵件地址", "components.UserList.deleteuser": "刪除使用者", "components.UserList.role": "角色", @@ -214,16 +203,16 @@ "components.Settings.SonarrModal.selectRootFolder": "設定根目錄", "components.Settings.RadarrModal.selectRootFolder": "設定根目錄", "components.Settings.RadarrModal.validationRootFolderRequired": "必須設定根目錄", - "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack 通知設定保存成功!", - "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook 通知設定保存成功!", - "components.Settings.Notifications.discordsettingssaved": "Discord 通知設定保存成功!", - "components.Settings.Notifications.emailsettingssaved": "電子郵件通知設定保存成功!", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack 通知設定儲存成功!", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook 通知設定儲存成功!", + "components.Settings.Notifications.discordsettingssaved": "Discord 通知設定儲存成功!", + "components.Settings.Notifications.emailsettingssaved": "電子郵件通知設定儲存成功!", "components.Settings.Notifications.chatId": "聊天室 ID", - "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover 通知設定保存失敗。", - "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack 通知設定保存失敗。", - "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook 通知設定保存失敗。", - "components.Settings.Notifications.discordsettingsfailed": "Discord 通知設定保存失敗。", - "components.Settings.Notifications.emailsettingsfailed": "電子郵件通知設定保存失敗。", + "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover 通知設定儲存失敗。", + "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack 通知設定儲存失敗。", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook 通知設定儲存失敗。", + "components.Settings.Notifications.discordsettingsfailed": "Discord 通知設定儲存失敗。", + "components.Settings.Notifications.emailsettingsfailed": "電子郵件通知設定儲存失敗。", "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "幫助", "components.Settings.SonarrModal.animerootfolder": "動漫根目錄", "components.Settings.SonarrModal.rootfolder": "根目錄", @@ -234,7 +223,7 @@ "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "請輸入有效的使用者或群組令牌", "components.Settings.menuJobs": "作業和快取", "components.Settings.toastApiKeyFailure": "生成應用程式密鑰出了點問題。", - "components.Settings.toastSettingsFailure": "保存設定中出了點問題。", + "components.Settings.toastSettingsFailure": "儲存設定中出了點問題。", "components.UserList.deleteconfirm": "確定要刪除這個使用者嗎?此使用者的所有儲存資料將被清除。", "components.Settings.SettingsAbout.Releases.releasedataMissing": "無法獲取軟體版本資料。", "components.UserList.passwordinfodescription": "設定應用程式網址以及啟用電子郵件通知,才能自動生成密碼。", @@ -265,7 +254,7 @@ "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} 變更日誌", "components.Settings.SettingsAbout.Releases.releases": "軟體版本", "components.Settings.plexsettings": "Plex 設定", - "components.RequestModal.selectseason": "季數選擇", + "components.RequestModal.selectseason": "請選擇季數", "components.RequestModal.requesttitle": "為 {title} 提出請求", "components.RequestModal.requestseasons": "提出請求", "components.RequestModal.requestadmin": "此請求將自動被批准。", @@ -305,10 +294,10 @@ "components.Settings.SonarrModal.animequalityprofile": "動漫品質設定", "components.Settings.SonarrModal.qualityprofile": "品質設定", "components.Settings.RadarrModal.qualityprofile": "品質設定", - "components.Settings.Notifications.telegramsettingsfailed": "Telegram 通知設定保存失敗。", - "components.Settings.Notifications.telegramsettingssaved": "Telegram 通知設定保存成功!", - "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover 通知設定保存成功!", - "components.UserList.created": "建立日期", + "components.Settings.Notifications.telegramsettingsfailed": "Telegram 通知設定儲存失敗。", + "components.Settings.Notifications.telegramsettingssaved": "Telegram 通知設定儲存成功!", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover 通知設定儲存成功!", + "components.UserList.created": "加入日期", "components.UserList.create": "建立", "components.Settings.SonarrModal.createsonarr": "新增 Sonarr 伺服器", "components.Settings.RadarrModal.createradarr": "新增 Radarr 伺服器", @@ -322,7 +311,7 @@ "components.StatusChacker.newversionDescription": "Overseerr 軟體已更新。請點擊以下的按鈕刷新頁面。", "components.RequestModal.requestcancelled": "{title} 的請求已被取消。", "components.RequestModal.AdvancedRequester.qualityprofile": "品質設定", - "components.RequestModal.AdvancedRequester.animenote": "*這是個動漫節目。", + "components.RequestModal.AdvancedRequester.animenote": "※這是個動漫節目。", "components.RequestModal.AdvancedRequester.advancedoptions": "進階選項", "components.RequestModal.AdvancedRequester.default": "{name}(預設)", "components.RequestModal.AdvancedRequester.destinationserver": "目標伺服器", @@ -345,7 +334,7 @@ "components.Settings.toastPlexRefresh": "載入中…", "components.Settings.serverpresetRefreshing": "載入中…", "components.Settings.SonarrModal.syncEnabled": "啟用掃描", - "components.UserList.userssaved": "使用者權限保存成功!", + "components.UserList.userssaved": "使用者權限儲存成功!", "components.Settings.hideAvailable": "隱藏可觀看的電影和電視節目", "components.Settings.SonarrModal.externalUrl": "外部網址", "components.Settings.RadarrModal.externalUrl": "外部網址", @@ -354,11 +343,8 @@ "components.Settings.toastPlexConnectingSuccess": "Plex 伺服器連線成功!", "components.Settings.serverRemote": "遠端", "components.Settings.serverLocal": "本地", - "components.TvDetails.opensonarr": "開啟 Sonarr 伺服器", - "components.MovieDetails.openradarr": "開啟 Radarr 伺服器", "components.MovieDetails.mark4kavailable": "標記 4K 版為可觀看", "components.MovieDetails.markavailable": "標記為可觀看", - "components.TvDetails.downloadstatus": "下載狀態", "components.Settings.RadarrModal.syncEnabled": "啟用掃描", "i18n.experimental": "實驗性", "components.UserList.bulkedit": "批量編輯", @@ -367,20 +353,17 @@ "components.NotificationTypeSelector.mediadeclined": "請求拒絕", "components.TvDetails.playonplex": "在 Plex 上觀看", "components.TvDetails.play4konplex": "在 Plex 上觀看 4K 版", - "components.TvDetails.opensonarr4k": "開啟 4K Sonarr 伺服器", - "components.MovieDetails.openradarr4k": "開啟 4K Radarr 伺服器", "components.MovieDetails.play4konplex": "在 Plex 上觀看 4K 版", "components.MovieDetails.playonplex": "在 Plex 上觀看", "components.PlexLoginButton.signinwithplex": "登入", "components.PlexLoginButton.signingin": "登入中…", "components.PermissionEdit.users": "管理使用者", - "components.PermissionEdit.settings": "設定管理", + "components.PermissionEdit.settings": "管理設定", "components.PermissionEdit.request4kTv": "提出 4K 電視節目請求", "components.PermissionEdit.request4kMovies": "提出 4K 電影請求", "components.PermissionEdit.request4k": "提出 4K 請求", "components.PermissionEdit.request": "提出請求", - "components.PermissionEdit.managerequests": "請求管理", - "components.MovieDetails.downloadstatus": "下載狀態", + "components.PermissionEdit.managerequests": "管理請求", "components.RequestBlock.profilechanged": "品質設定", "components.PermissionEdit.advancedrequest": "進階請求", "components.RequestModal.requestedited": "{title} 的請求編輯成功!", @@ -393,14 +376,14 @@ "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} 快取清除成功!", "components.Settings.SettingsJobsCache.cachemisses": "失誤數", "components.Settings.SettingsJobsCache.cachehits": "擊中數", - "components.Settings.SettingsJobsCache.cachename": "快取名", + "components.Settings.SettingsJobsCache.cachename": "快取名稱", "components.Settings.SettingsJobsCache.runnow": "執行", "components.Settings.SettingsJobsCache.nextexecution": "下一次執行時間", "components.Settings.SettingsJobsCache.jobtype": "作業類型", "components.Settings.SettingsJobsCache.jobstarted": "{jobname} 已開始運行。", "components.Settings.SettingsJobsCache.jobcancelled": "{jobname}已被取消。", "components.Settings.SettingsJobsCache.jobs": "作業", - "components.Settings.SettingsJobsCache.jobname": "作業名", + "components.Settings.SettingsJobsCache.jobname": "作業名稱", "components.Settings.SettingsJobsCache.flushcache": "清除快取", "components.Settings.SettingsJobsCache.canceljob": "取消作業", "components.Settings.SettingsJobsCache.cache": "快取", @@ -414,23 +397,18 @@ "components.Settings.toastPlexRefreshSuccess": "獲取 Plex 伺服器列表成功!", "components.Settings.toastPlexRefreshFailure": "獲取 Plex 伺服器列表失敗。", "components.Settings.toastPlexConnectingFailure": "Plex 伺服器連線失敗。", - "components.TvDetails.mark4kavailable": "標記 4K 版為可觀看", - "components.TvDetails.markavailable": "標記為可觀看", - "components.TvDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這個電視節目的資料。如果這個節目存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", - "components.MovieDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這部電影的資料。如果這部電影存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", - "components.TvDetails.allseasonsmarkedavailable": "*每季將被標記為可觀看。", "components.Settings.csrfProtectionHoverTip": "除非您了解此功能,請勿啟用它!", "components.UserList.users": "使用者", - "components.Settings.applicationTitle": "應用程式名", + "components.Settings.applicationTitle": "應用程式名稱", "components.Search.search": "搜尋", "components.Setup.setup": "配置", "components.Discover.discover": "探索", - "components.AppDataWarning.dockerVolumeMissingDescription": "您必須使用繫結掛載(bind mount)來繫結主機上的目錄跟 Docker 容器內的 {appDataPath} 目錄,才能保存 Overseerr 的配置和數據。", + "components.AppDataWarning.dockerVolumeMissingDescription": "您必須使用繫結掛載(bind mount)來繫結主機上的目錄跟 Docker 容器內的 {appDataPath} 目錄,才能儲存 Overseerr 的配置和數據。", "components.RequestModal.AdvancedRequester.requestas": "請求者", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "必須刪除結尾斜線", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "必須刪除結尾斜線", "components.Settings.validationApplicationUrlTrailingSlash": "必須刪除結尾斜線", - "components.Settings.validationApplicationTitle": "請輸入應用程式名", + "components.Settings.validationApplicationTitle": "請輸入應用程式名稱", "components.Settings.validationApplicationUrl": "請輸入有效的網址", "components.Settings.SonarrModal.validationApplicationUrl": "請輸入有效的網址", "components.Settings.RadarrModal.validationApplicationUrl": "請輸入有效的網址", @@ -458,11 +436,11 @@ "components.NotificationTypeSelector.mediarequestedDescription": "當使用者提出需要管理員批准的請求時發送通知。", "components.NotificationTypeSelector.mediafailedDescription": "當 Radarr 或 Sonarr 處理請求失敗時發送通知。", "components.NotificationTypeSelector.mediadeclinedDescription": "當請求拒被絕時發送通知。", - "components.PermissionEdit.request4kDescription": "授予提出 4K 請求的權限。", - "components.PermissionEdit.request4kMoviesDescription": "授予為電影提出 4K 請求的權限。", + "components.PermissionEdit.request4kDescription": "授予提出 4K 媒體請求的權限。", + "components.PermissionEdit.request4kMoviesDescription": "授予提出 4K 電影請求的權限。", "components.PermissionEdit.request4kTvDescription": "授予提出 4K 電視節目請求的權限。", - "components.PermissionEdit.requestDescription": "授予提出非 4K 請求的權限。", - "components.PermissionEdit.viewrequestsDescription": "授予查看其他使用者的請求的權限。", + "components.PermissionEdit.requestDescription": "授予提出非 4K 媒體請求的權限。", + "components.PermissionEdit.viewrequestsDescription": "授予查看其他使用者提出的媒體請求的權限。", "components.Settings.SonarrModal.validationLanguageProfileRequired": "必須設定語言", "components.Settings.SonarrModal.testFirstLanguageProfiles": "請先測試連線", "components.Settings.SonarrModal.selectLanguageProfile": "設定語言", @@ -470,30 +448,29 @@ "components.Settings.SonarrModal.languageprofile": "語言設定", "components.Settings.SonarrModal.animelanguageprofile": "動漫語言設定", "components.RequestModal.AdvancedRequester.languageprofile": "語言設定", - "components.PermissionEdit.settingsDescription": "授予管理 Overseerr 設定的權限。", + "components.PermissionEdit.settingsDescription": "授予管理全域設定的權限。", "components.PermissionEdit.usersDescription": "授予管理使用者的權限。有此權限的使用者不能編輯管理員或授予管理員權限。", "components.PermissionEdit.autoapproveSeriesDescription": "自動批准非 4K 電視節目請求。", "components.PermissionEdit.autoapproveMoviesDescription": "自動批准非 4K 電影請求。", - "components.PermissionEdit.autoapproveDescription": "自動批准所有非 4K 請求。", - "components.PermissionEdit.advancedrequestDescription": "授予使用進階請求選項的權限。", + "components.PermissionEdit.autoapproveDescription": "自動批准所有非 4K 媒體請求。", + "components.PermissionEdit.advancedrequestDescription": "授予使用進階媒體請求選項的權限。", "components.PermissionEdit.adminDescription": "授予最高權限;旁路所有權限檢查。", - "components.PermissionEdit.managerequestsDescription": "授予管理請求的權限,以及所有自動批准的權限。", + "components.PermissionEdit.managerequestsDescription": "授予管理媒體請求的權限,以及所有自動批准的權限。", "components.Settings.SettingsJobsCache.cacheDescription": "外部應用程式介面(external API)請求將存到快取記憶體,以減少 API 呼叫次數。", "components.Settings.librariesRemaining": "媒體庫剩餘數: {count}", - "components.Settings.Notifications.sendSilentlyTip": "發送沒有聲音警報的通知", + "components.Settings.Notifications.sendSilentlyTip": "發送沒有警報音的通知", "components.Settings.Notifications.sendSilently": "無聲通知", - "components.UserList.sortCreated": "建立日期", + "components.UserList.sortCreated": "加入日期", "components.UserList.sortDisplayName": "顯示名稱", "components.UserList.sortRequests": "請求數", "components.PermissionEdit.autoapprove4kSeriesDescription": "自動批准 4K 電視節目請求。", "components.PermissionEdit.autoapprove4kSeries": "4K 電視節目自動批准", "components.PermissionEdit.autoapprove4kMoviesDescription": "自動批准 4K 電影請求。", "components.PermissionEdit.autoapprove4kMovies": "4K 電影自動批准", - "components.PermissionEdit.autoapprove4kDescription": "自動批准所有 4K 請求。", + "components.PermissionEdit.autoapprove4kDescription": "自動批准所有 4K 媒體請求。", "components.PermissionEdit.autoapprove4k": "自動批准 4K", - "components.UserList.sortUpdated": "最後更新日期", - "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "權限設定保存成功!", - "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "設定保存成功!", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "權限設定儲存成功!", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "設定儲存成功!", "components.UserProfile.recentrequests": "最新請求", "components.UserProfile.UserSettings.UserPermissions.permissions": "權限設定", "components.UserProfile.UserSettings.menuPermissions": "權限", @@ -506,8 +483,8 @@ "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "確認密碼", "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "通知設定", "components.UserList.edituser": "編輯使用者權限", - "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet 通知設定保存失敗。", - "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet 通知設定保存成功!", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet 通知設定儲存失敗。", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet 通知設定儲存成功!", "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "請輸入 API 令牌", "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "使用者 ID", "components.UserProfile.ProfileHeader.profile": "顯示個人資料", @@ -516,9 +493,9 @@ "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "一般設定", "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex 使用者", "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "本地使用者", - "components.UserList.userfail": "使用者權限保存中出了點問題。", - "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "保存設定中出了點問題。", - "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "保存設定中出了點問題。", + "components.UserList.userfail": "使用者權限儲存中出了點問題。", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "儲存設定中出了點問題。", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "儲存設定中出了點問題。", "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "啟用通知", "components.Settings.Notifications.NotificationsPushbullet.accessToken": "API 令牌", "components.Layout.UserDropdown.settings": "設定", @@ -534,9 +511,8 @@ "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "您的使用者 ID 號碼", "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "重設密碼中出了點問題。", "components.RequestModal.SearchByNameModal.notvdbiddescription": "無法自動配對您的請求。請從以下列表中選擇正確的媒體項。", - "components.CollectionDetails.requestswillbecreated4k": "為以下的電影提出 4K 請求:", "components.CollectionDetails.requestcollection4k": "提出 4K 系列請求", - "components.Settings.trustProxyTip": "使用代理伺服器時,允許 Overseerr 探明客戶端 IP 位址(Overseerr 必須重新啟動)", + "components.Settings.trustProxyTip": "使用代理伺服器時,允許 Overseerr 註冊客戶端的正確 IP 位址(Overseerr 必須重新啟動)", "components.Settings.csrfProtectionTip": "設定外部訪問權限為只讀(Overseerr 必須重新啟動)", "components.ResetPassword.requestresetlinksuccessmessage": "通過電子郵件發送了密碼重設鏈接。", "components.ResetPassword.resetpasswordsuccessmessage": "密碼重設成功!", @@ -548,7 +524,7 @@ "components.Discover.upcomingtv": "即將上映的電視節目", "components.Settings.webhook": "Webhook", "components.Settings.email": "電子郵件", - "components.Settings.generalsettingsDescription": "Overseerr 的全局和預設設定。", + "components.Settings.generalsettingsDescription": "Overseerr 的全域和預設設定。", "components.Settings.notificationAgentSettingsDescription": "設定通知類型和代理服務。", "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "以地區可用性篩選結果", "components.Settings.regionTip": "以地區可用性篩選結果", @@ -580,9 +556,9 @@ "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "請輸入聊天室 ID", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "聊天室 ID", "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "無聲通知", - "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "發送沒有聲音警報的通知", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "發送沒有警報音的通知", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "先建立一個聊天室以及把 @get_id_bot 加到聊天室,然後在聊天室裡發出 /my_id 命令", - "components.Settings.Notifications.botUsername": "Bot 機器人名", + "components.Settings.Notifications.botUsername": "Bot 機器人名稱", "components.Discover.NetworkSlider.networks": "電視網", "components.Discover.StudioSlider.studios": "製作公司", "components.Settings.Notifications.validationUrl": "請輸入有效的網址", @@ -600,14 +576,14 @@ "components.Discover.DiscoverTvLanguage.languageSeries": "{language}電視節目", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language}電影", "components.UserProfile.ProfileHeader.userid": "使用者 ID:{userid}", - "components.UserProfile.ProfileHeader.joindate": "建立日期:{joindate}", + "components.UserProfile.ProfileHeader.joindate": "加入日期:{joindate}", "components.Settings.SettingsUsers.localLogin": "允許本地登入", "components.Settings.SettingsUsers.defaultPermissions": "預設權限", - "components.Settings.SettingsUsers.userSettingsDescription": "關於使用者的全局和預設設定。", + "components.Settings.SettingsUsers.userSettingsDescription": "關於使用者的全域和預設設定。", "components.Settings.SettingsUsers.userSettings": "使用者設定", "components.Settings.menuUsers": "使用者", - "components.Settings.SettingsUsers.toastSettingsSuccess": "使用者設定保存成功!", - "components.Settings.SettingsUsers.toastSettingsFailure": "保存設定中出了點問題。", + "components.Settings.SettingsUsers.toastSettingsSuccess": "使用者設定儲存成功!", + "components.Settings.SettingsUsers.toastSettingsFailure": "儲存設定中出了點問題。", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "當使用者提出自動批准的請求時發送通知。", "components.NotificationTypeSelector.mediaAutoApproved": "請求自動批准", "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "您不能編輯自己的權限。", @@ -625,7 +601,6 @@ "components.Discover.MovieGenreList.moviegenres": "電影類型", "components.Discover.TvGenreList.seriesgenres": "電視節目類型", "components.Settings.partialRequestsEnabled": "允許不完整的電視節目請求", - "components.RequestModal.requestall": "提出請求", "components.RequestModal.alreadyrequested": "已經有請求", "components.Settings.SettingsLogs.time": "時間戳", "components.Settings.SettingsLogs.resumeLogs": "恢復", @@ -659,7 +634,7 @@ "components.Settings.SettingsAbout.about": "關於 Overseerr", "components.Settings.cacheImages": "啟用圖像緩存", "components.Settings.SettingsLogs.logsDescription": "您也能直接查看 stdout 數據流或位置於 {configDir}/logs/overseerr.log 的日誌檔案。", - "components.Settings.cacheImagesTip": "把所有的圖像優化和保存到快取記憶體(需要大量的磁碟空間)", + "components.Settings.cacheImagesTip": "把所有的圖像優化和儲存到快取記憶體(需要大量的磁碟空間)", "components.Settings.SettingsLogs.logDetails": "日誌詳細信息", "components.Settings.SettingsLogs.extraData": "附加數據", "components.Settings.SettingsLogs.copyToClipboard": "複製到剪貼板", @@ -670,8 +645,8 @@ "components.PersonDetails.lifespan": "{birthdate}-{deathdate}", "components.PersonDetails.alsoknownas": "別名:{names}", "i18n.delimitedlist": "{a}、{b}", - "components.Settings.SettingsUsers.tvRequestLimitLabel": "電視節目請求全局限制", - "components.Settings.SettingsUsers.movieRequestLimitLabel": "電影請求全局限制", + "components.Settings.SettingsUsers.tvRequestLimitLabel": "電視節目請求全域限制", + "components.Settings.SettingsUsers.movieRequestLimitLabel": "電影請求全域限制", "components.RequestModal.QuotaDisplay.seasonlimit": "季數", "components.RequestModal.QuotaDisplay.season": "電視節目季數", "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {電影請求剩餘數不足} other {剩餘 # 個{type}請求}}", @@ -687,7 +662,7 @@ "components.UserProfile.requestsperdays": "剩餘 {limit}", "components.QuotaSelector.unlimited": "無限", "components.RequestModal.QuotaDisplay.movie": "電影", - "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "覆寫全局限制", + "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "覆寫全域限制", "components.UserProfile.unlimited": "無限", "components.UserProfile.totalrequests": "請求總數", "components.UserProfile.seriesrequest": "電視節目請求", @@ -697,8 +672,8 @@ "i18n.test": "測試", "i18n.status": "狀態", "i18n.showingresults": "{from}{to} 列(共 {total} 列)", - "i18n.saving": "保存中…", - "i18n.save": "保存", + "i18n.saving": "儲存中…", + "i18n.save": "儲存", "i18n.resultsperpage": "每頁顯示 {pageSize} 列", "i18n.requesting": "提出請求中…", "i18n.request4k": "提出 4K 請求", @@ -745,12 +720,12 @@ "components.RequestCard.deleterequest": "刪除請求", "components.Settings.Notifications.botUsernameTip": "允許使用者也把機器人加到自己的聊天室以及設定自己的通知", "components.RequestModal.pendingapproval": "您的請求正在等待管理員批准。", - "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Telegram 通知設定保存失敗。", - "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "電子郵件通知設定保存失敗。", - "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Discord 通知設定保存失敗。", - "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram 通知設定保存成功!", - "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "電子郵件通知設定保存成功!", - "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord 通知設定保存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Telegram 通知設定儲存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "電子郵件通知設定儲存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Discord 通知設定儲存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram 通知設定儲存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "電子郵件通知設定儲存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord 通知設定儲存成功!", "components.UserProfile.UserSettings.UserNotificationSettings.email": "電子郵件", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "使用 OpenPGP 電子郵件加密", "components.Settings.Notifications.validationPgpPassword": "請輸入 PGP 解密密碼", @@ -783,18 +758,18 @@ "components.Settings.SonarrModal.enableSearch": "啟用自動搜尋", "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "網路推送", "components.Settings.webpush": "網路推送", - "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "網路推送通知設定保存成功!", - "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "網路推送通知設定保存失敗。", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "網路推送通知設定儲存成功!", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "網路推送通知設定儲存失敗。", "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "顯示語言", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "啟用通知", - "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea 通知設定保存成功!", - "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea 通知設定保存失敗。", + "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea 通知設定儲存成功!", + "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea 通知設定儲存失敗。", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook 網址", "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "請輸入有效的網址", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "啟用通知", "components.Settings.is4k": "4K", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "網路推送通知設定保存失敗。", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "網路推送通知設定保存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "網路推送通知設定儲存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "網路推送通知設定儲存成功!", "components.Settings.Notifications.toastEmailTestSuccess": "電子郵件測試通知已發送!", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "網路推送測試通知已發送!", "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram 測試通知已發送!", @@ -805,7 +780,7 @@ "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea 測試通知已發送!", "components.Settings.noDefault4kServer": "您必須指定一個 4K {serverType} 伺服器為預設,才能處理 4K 的{mediaType}請求。", "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "不使用 default 預設設定檔才必須輸入", - "components.Settings.Notifications.NotificationsLunaSea.profileName": "設定檔名", + "components.Settings.Notifications.NotificationsLunaSea.profileName": "設定檔名稱", "components.Settings.Notifications.toastTelegramTestSending": "發送 Telegram 測試通知中…", "components.Settings.Notifications.toastEmailTestSending": "發送電子郵件測試通知中…", "components.Settings.Notifications.toastDiscordTestSending": "發送 Discord 測試通知中…", @@ -885,8 +860,140 @@ "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "每 {jobScheduleHours} 小時", "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "頻率", "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "每 {jobScheduleMinutes} 分鐘", - "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "保存作業設定中出了點問題。", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "儲存作業設定中出了點問題。", "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "作業編輯成功!", "components.Settings.SettingsAbout.runningDevelop": "您正在使用 Overseerr 的 develop 開發版。我們只建議開發者和協助測試的人員使用。", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.season": "第 {seasonNumber} 季", + "components.IssueList.IssueItem.problemepisode": "有問題的集數", + "components.IssueDetails.problemseason": "有問題的季數", + "components.IssueModal.CreateIssueModal.season": "第 {seasonNumber} 季", + "components.Layout.Sidebar.issues": "問題", + "components.IssueDetails.deleteissue": "刪除問題", + "components.IssueDetails.issuetype": "類型", + "components.IssueDetails.allseasons": "所有季數", + "components.IssueDetails.IssueDescription.description": "說明", + "components.IssueDetails.IssueDescription.edit": "編輯說明", + "components.IssueDetails.IssueDescription.deleteissue": "刪除問題", + "components.IssueDetails.deleteissueconfirm": "確定要刪除這個問題嗎?", + "components.IssueDetails.issuepagetitle": "問題", + "components.IssueDetails.lastupdated": "最後更新時間", + "components.IssueDetails.unknownissuetype": "不明", + "components.IssueList.issues": "問題", + "components.IssueList.IssueItem.viewissue": "查看問題", + "components.IssueModal.CreateIssueModal.episode": "第 {episodeNumber} 集", + "components.IssueModal.CreateIssueModal.problemepisode": "有問題的集數", + "components.IssueModal.CreateIssueModal.problemseason": "有問題的季數", + "components.IssueModal.CreateIssueModal.toastviewissue": "查看問題", + "components.ManageSlideOver.manageModalClearMedia": "清除儲存資料", + "components.ManageSlideOver.manageModalRequests": "請求", + "components.ManageSlideOver.manageModalNoRequests": "沒有請求。", + "components.ManageSlideOver.openarr": "開啟 {arr} 伺服器", + "components.ManageSlideOver.openarr4k": "開啟 4K {arr} 伺服器", + "components.ManageSlideOver.movie": "部電影", + "components.ManageSlideOver.markavailable": "標記為可觀看", + "components.IssueModal.issueAudio": "音頻", + "components.ManageSlideOver.downloadstatus": "下載狀態", + "components.ManageSlideOver.allseasonsmarkedavailable": "※所有季數將被標記為可觀看。", + "components.IssueModal.CreateIssueModal.allepisodes": "所有集數", + "components.ManageSlideOver.manageModalClearMediaWarning": "※這將會刪除包括使用者請求在內所有有關這{mediaType}的資料。如果這{mediaType}存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", + "components.ManageSlideOver.mark4kavailable": "標記 4K 版為可觀看", + "components.IssueModal.issueSubtitles": "字幕", + "components.IssueModal.issueOther": "其他", + "components.ManageSlideOver.tvshow": "個電視節目", + "components.ManageSlideOver.manageModalTitle": "管理{mediaType}", + "components.IssueModal.issueVideo": "視頻", + "components.IssueList.IssueItem.issuetype": "類型", + "components.IssueDetails.problemepisode": "有問題的集數", + "components.IssueList.IssueItem.unknownissuetype": "不明", + "components.IssueList.IssueItem.issuestatus": "狀態", + "components.IssueModal.CreateIssueModal.allseasons": "所有季數", + "components.IssueDetails.allepisodes": "所有集數", + "components.IssueDetails.episode": "第 {episodeNumber} 集", + "components.PermissionEdit.viewissues": "查看問題", + "components.PermissionEdit.manageissuesDescription": "授予管理媒體問題的權限。", + "components.PermissionEdit.viewissuesDescription": "授予查看其他使用者提出的媒體問題的權限。", + "components.PermissionEdit.manageissues": "管理問題", + "components.IssueDetails.comments": "評論", + "components.IssueDetails.nocomments": "沒有評論。", + "components.IssueDetails.openedby": "#{issueId} 由 {username} {relativeTime}報告", + "components.IssueDetails.toastissuedeleted": "問題刪除成功!", + "components.IssueDetails.toaststatusupdated": "問題狀態編輯成功!", + "components.IssueDetails.toastissuedeletefailed": "刪除問題狀態中出了點問題。", + "components.IssueList.IssueItem.openeduserdate": "{user}({date})", + "components.IssueModal.CreateIssueModal.issomethingwrong": "{title} 有問題嗎?", + "components.IssueModal.CreateIssueModal.providedetail": "請詳細解釋您遇到的問題。", + "components.IssueModal.CreateIssueModal.submitissue": "報告問題", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "提出問題報告中出了點問題。", + "components.NotificationTypeSelector.userissueresolvedDescription": "當您報告的問題解決時取得通知。", + "components.PermissionEdit.createissues": "報告問題", + "components.PermissionEdit.createissuesDescription": "授予報告媒體問題的權限。", + "components.IssueDetails.leavecomment": "發表評論", + "components.IssueDetails.toasteditdescriptionfailed": "編輯問題說明中出了點問題。", + "components.IssueList.showallissues": "查看所有問題", + "components.IssueDetails.toaststatusupdatefailed": "編輯問題狀態中出了點問題。", + "components.IssueModal.CreateIssueModal.reportissue": "報告問題", + "components.IssueList.IssueItem.opened": "報告者", + "components.IssueModal.CreateIssueModal.whatswrong": "請解釋您遇到的問題。", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "請輸入問題說明", + "components.NotificationTypeSelector.issueresolved": "問題解決", + "components.NotificationTypeSelector.issuecreated": "問題報告", + "components.NotificationTypeSelector.issuecreatedDescription": "當使用者報告問題時發送通知。", + "components.NotificationTypeSelector.issuecomment": "問題評論", + "components.NotificationTypeSelector.adminissuecommentDescription": "當其他使用者加新評論時取得通知。", + "components.NotificationTypeSelector.issuecommentDescription": "當問題有新評論時發送通知。", + "components.NotificationTypeSelector.issueresolvedDescription": "當問題解決時發送通知。", + "components.IssueDetails.openinarr": "開啟 {arr} 伺服器", + "i18n.resolved": "已解決", + "i18n.open": "未解決", + "components.IssueList.sortAdded": "最新", + "components.IssueList.sortModified": "最後修改", + "components.IssueDetails.toasteditdescriptionsuccess": "問題說明編輯成功!", + "components.NotificationTypeSelector.userissuecreatedDescription": "當其他使用者報告問題時取得通知。", + "components.IssueDetails.IssueComment.delete": "刪除評論", + "components.IssueDetails.IssueComment.edit": "編輯評論", + "components.IssueDetails.IssueComment.validationComment": "請輸入評論", + "components.IssueDetails.IssueComment.areyousuredelete": "確定要刪除這個評論嗎?", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "為 {title} 報告問題成功!", + "components.NotificationTypeSelector.userissuecommentDescription": "當您報告的問題有新評論時取得通知。", + "components.IssueDetails.IssueComment.postedby": "由 {username} {relativeTime}發表", + "components.IssueDetails.IssueComment.postedbyedited": "由 {username} {relativeTime}發表(已編輯)", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "請輸入 API 令牌", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "請輸入應用程式 API 令牌", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet 通知設定儲存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "應用程式 API 令牌", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover 通知設定儲存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "API 令牌", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "您 30 個字符的使用者或群組識別碼", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover 通知設定儲存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "從您的帳號設定取得 API 令牌", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet 通知設定儲存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "建立一個 {applicationTitle} 專用的應用程式", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "使用者或群組令牌", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "請輸入有效的使用者或群組令牌", + "components.IssueDetails.openin4karr": "開啟 4K {arr} 伺服器", + "components.IssueDetails.play4konplex": "在 Plex 上觀看 4K 版", + "components.IssueDetails.playonplex": "在 Plex 上觀看", + "components.IssueList.IssueItem.episodes": "集數", + "components.IssueList.IssueItem.seasons": "季數", + "components.IssueDetails.closeissue": "關閉問題", + "components.IssueDetails.closeissueandcomment": "發表評論及關閉問題", + "components.IssueDetails.reopenissueandcomment": "發表評論及重新開啟問題", + "components.IssueDetails.reopenissue": "重新開啟問題", + "components.ManageSlideOver.manageModalIssues": "未解決問題", + "components.IssueModal.CreateIssueModal.extras": "特輯", + "components.NotificationTypeSelector.issuereopened": "問題重新開啟", + "components.NotificationTypeSelector.adminissuereopenedDescription": "當其他使用者重新開啟問題時取得通知。", + "components.NotificationTypeSelector.adminissueresolvedDescription": "當其他使用者解決問題時取得通知。", + "components.NotificationTypeSelector.issuereopenedDescription": "當問題重新開啟時發送通知。", + "components.NotificationTypeSelector.userissuereopenedDescription": "當您報告的問題重新開啟時取得通知。", + "components.RequestModal.requestmovies4k": "提出 4K 請求", + "components.RequestModal.requestseasons4k": "提出 4K 請求", + "components.RequestModal.requestmovies": "提出請求", + "components.MovieDetails.productioncountries": "製作國家", + "components.RequestModal.selectmovies": "請選擇電影", + "components.TvDetails.productioncountries": "製作國家", + "components.IssueDetails.commentplaceholder": "發表評論…", + "components.RequestModal.requestApproved": "{title} 的請求已被批准。", + "components.RequestModal.approve": "批准請求" } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ab881702b..afb3fc69b 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -47,6 +47,8 @@ const loadLocaleData = (locale: AvailableLocale): Promise => { return import('../i18n/locale/nb_NO.json'); case 'nl': return import('../i18n/locale/nl.json'); + case 'pl': + return import('../i18n/locale/pl.json'); case 'pt-BR': return import('../i18n/locale/pt_BR.json'); case 'pt-PT': diff --git a/src/pages/issues/[issueId]/index.tsx b/src/pages/issues/[issueId]/index.tsx new file mode 100644 index 000000000..48aadebf7 --- /dev/null +++ b/src/pages/issues/[issueId]/index.tsx @@ -0,0 +1,21 @@ +import { NextPage } from 'next'; +import React from 'react'; +import IssueDetails from '../../../components/IssueDetails'; +import useRouteGuard from '../../../hooks/useRouteGuard'; +import { Permission } from '../../../hooks/useUser'; + +const IssuePage: NextPage = () => { + useRouteGuard( + [ + Permission.MANAGE_ISSUES, + Permission.CREATE_ISSUES, + Permission.VIEW_ISSUES, + ], + { + type: 'or', + } + ); + return ; +}; + +export default IssuePage; diff --git a/src/pages/issues/index.tsx b/src/pages/issues/index.tsx new file mode 100644 index 000000000..f352b89b0 --- /dev/null +++ b/src/pages/issues/index.tsx @@ -0,0 +1,21 @@ +import { NextPage } from 'next'; +import React from 'react'; +import IssueList from '../../components/IssueList'; +import useRouteGuard from '../../hooks/useRouteGuard'; +import { Permission } from '../../hooks/useUser'; + +const IssuePage: NextPage = () => { + useRouteGuard( + [ + Permission.MANAGE_ISSUES, + Permission.CREATE_ISSUES, + Permission.VIEW_ISSUES, + ], + { + type: 'or', + } + ); + return ; +}; + +export default IssuePage; diff --git a/src/pages/profile/settings/notifications/pushbullet.tsx b/src/pages/profile/settings/notifications/pushbullet.tsx new file mode 100644 index 000000000..12afd9413 --- /dev/null +++ b/src/pages/profile/settings/notifications/pushbullet.tsx @@ -0,0 +1,17 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushbullet from '../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet'; + +const NotificationsPage: NextPage = () => { + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/pages/profile/settings/notifications/pushover.tsx b/src/pages/profile/settings/notifications/pushover.tsx new file mode 100644 index 000000000..83b8d71db --- /dev/null +++ b/src/pages/profile/settings/notifications/pushover.tsx @@ -0,0 +1,17 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushover from '../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover'; + +const NotificationsPage: NextPage = () => { + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/pages/users/[userId]/settings/notifications/pushbullet.tsx b/src/pages/users/[userId]/settings/notifications/pushbullet.tsx new file mode 100644 index 000000000..cd7ca10c8 --- /dev/null +++ b/src/pages/users/[userId]/settings/notifications/pushbullet.tsx @@ -0,0 +1,20 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushbullet from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet'; +import useRouteGuard from '../../../../../hooks/useRouteGuard'; +import { Permission } from '../../../../../hooks/useUser'; + +const NotificationsPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_USERS); + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/pages/users/[userId]/settings/notifications/pushover.tsx b/src/pages/users/[userId]/settings/notifications/pushover.tsx new file mode 100644 index 000000000..b37a866f7 --- /dev/null +++ b/src/pages/users/[userId]/settings/notifications/pushover.tsx @@ -0,0 +1,20 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushover from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover'; +import useRouteGuard from '../../../../../hooks/useRouteGuard'; +import { Permission } from '../../../../../hooks/useUser'; + +const NotificationsPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_USERS); + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/styles/globals.css b/src/styles/globals.css index fec0c7127..de023b666 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2,457 +2,463 @@ @tailwind components; @tailwind utilities; -html { - min-height: calc(100% + env(safe-area-inset-top)); - padding: env(safe-area-inset-top) env(safe-area-inset-right) - env(safe-area-inset-bottom) env(safe-area-inset-left); -} - -body { - @apply bg-gray-900; -} - -.searchbar { - padding-top: env(safe-area-inset-top); - height: calc(4rem + env(safe-area-inset-top)); -} - -.sidebar { - @apply border-r border-gray-700; - padding-top: env(safe-area-inset-top); - padding-left: env(safe-area-inset-left); - background: linear-gradient(180deg, rgba(31, 41, 55, 1) 0%, #131928 100%); -} - -.slideover { - padding-top: calc(1.5rem + env(safe-area-inset-top)); - padding-bottom: 1.5rem; -} - -.sidebar-close-button { - top: env(safe-area-inset-top); -} - -.absolute-top-shift { - top: calc(-4rem - env(safe-area-inset-top)); -} - -.min-h-screen-shift { - min-height: calc(100vh + env(safe-area-inset-top)); -} - -.plex-button { - @apply flex justify-center w-full px-4 py-2 text-sm font-medium text-center text-white transition duration-150 ease-in-out bg-indigo-600 border border-transparent rounded-md disabled:opacity-50; - background-color: #cc7b19; -} - -.plex-button:hover { - background: #f19a30; -} - -ul.cards-vertical, -ul.cards-horizontal { - @apply grid gap-4; -} - -ul.cards-vertical { - grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr)); -} - -ul.cards-horizontal { - grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr)); -} - -.slider-header { - @apply relative flex mt-6 mb-4; -} - -.slider-title { - @apply inline-flex items-center text-xl font-bold leading-7 text-gray-300 sm:text-2xl sm:leading-9 sm:truncate; -} - -a.slider-title { - @apply transition duration-300 hover:text-white; -} - -a.slider-title svg { - @apply w-6 h-6 ml-2; -} - -.media-page { - @apply relative px-4 -mx-4 bg-center bg-cover; - margin-top: calc(-4rem - env(safe-area-inset-top)); - padding-top: calc(4rem + env(safe-area-inset-top)); -} - -.media-page-bg-image { - @apply absolute inset-0 w-full h-full; - z-index: -10; -} - -.media-header { - @apply flex flex-col items-center pt-4 xl:flex-row xl:items-end; -} - -.media-poster { - @apply w-32 overflow-hidden rounded shadow md:rounded-lg md:shadow-2xl md:w-44 xl:w-52 xl:mr-4; -} - -.media-status { - @apply mb-2 space-x-2; -} - -.media-title { - @apply flex flex-col flex-1 mt-4 text-center text-white xl:mr-4 xl:mt-0 xl:text-left; -} - -.media-title > h1 { - @apply text-2xl font-bold xl:text-4xl; -} - -h1 > .media-year { - @apply text-2xl; -} - -.media-attributes { - @apply mt-1 text-xs text-gray-300 sm:text-sm xl:text-base xl:mt-0; -} - -.media-attributes a { - @apply transition duration-300 hover:text-white hover:underline; -} - -.media-actions { - @apply relative flex flex-wrap items-center justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap xl:mt-0; -} - -.media-actions > * { - @apply mb-3 sm:mb-0; -} - -.media-overview { - @apply flex flex-col pt-8 pb-4 text-white lg:flex-row; -} - -.media-overview-left { - @apply flex-1 lg:mr-8; -} - -.tagline { - @apply mb-4 text-xl italic text-gray-400 lg:text-2xl; -} - -.media-overview h2 { - @apply text-xl font-bold text-gray-300 sm:text-2xl; -} - -.media-overview p { - @apply pt-2 text-sm text-gray-400 sm:text-base; -} - -ul.media-crew { - @apply grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3; -} - -ul.media-crew > li { - @apply flex flex-col col-span-1 font-bold text-gray-300; -} - -a.crew-name, -.media-fact-value a, -.media-fact-value button { - @apply font-normal text-gray-400 transition duration-300 hover:underline hover:text-gray-100; -} - -.media-overview-right { - @apply w-full mt-8 lg:w-80 lg:mt-0; -} - -.media-facts { - @apply text-sm font-bold text-gray-300 bg-gray-900 border border-gray-700 rounded-lg shadow; -} - -.media-fact { - @apply flex justify-between px-4 py-2 border-b border-gray-700 last:border-b-0; -} - -.media-fact-value { - @apply ml-2 text-sm font-normal text-right text-gray-400; -} - -.media-ratings { - @apply flex items-center justify-center px-4 py-2 font-medium border-b border-gray-700 last:border-b-0; -} - -.media-rating { - @apply flex items-center mr-4 last:mr-0; -} - -.error-message { - @apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300; -} - -.heading { - @apply text-2xl font-bold leading-8 text-gray-100; -} - -.description { - @apply max-w-4xl mt-1 text-sm leading-5 text-gray-400; -} - -img.avatar-sm { - @apply w-5 h-5 mr-1.5 rounded-full transition duration-300 scale-100 transform-gpu group-hover:scale-105; -} - -.card-field { - @apply flex items-center py-0.5 sm:py-1 text-sm truncate; -} - -.card-field-name { - @apply mr-2 font-bold; -} - -.card-field a { - @apply transition duration-300 hover:text-white hover:underline; -} - -.section { - @apply mt-6 mb-10 text-white; -} - -.form-row { - @apply max-w-6xl mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start; -} - -.form-input { - @apply text-sm text-white sm:col-span-2; -} - -.form-input-field { - @apply flex max-w-xl rounded-md shadow-sm; -} - -.actions { - @apply pt-5 mt-8 text-white border-t border-gray-700; -} - -label, -.group-label { - @apply block mb-1 text-sm font-bold leading-5 text-gray-400; -} - -label.checkbox-label { - @apply sm:mt-1; -} - -label.text-label { - @apply sm:mt-2; -} - -label a { - @apply text-gray-100 transition duration-300 hover:text-white hover:underline; -} - -.label-required { - @apply ml-1 text-red-500; -} - -.label-tip { - @apply block font-medium text-gray-500; -} - -button, -input, -select, -textarea { - @apply disabled:cursor-not-allowed; -} - -input[type='checkbox'] { - @apply w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md; -} - -input[type='text'], -input[type='password'], -select, -textarea { - @apply flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md sm:text-sm sm:leading-5; -} - -input.rounded-l-only, -select.rounded-l-only, -textarea.rounded-l-only { - @apply rounded-r-none; -} - -input.rounded-r-only, -select.rounded-r-only, -textarea.rounded-r-only { - @apply rounded-l-none; -} - -input.short { - @apply w-20; -} - -select.short { - @apply w-min; -} - -button > span { - @apply whitespace-nowrap; -} - -button.input-action { - @apply relative inline-flex items-center px-3 sm:px-3.5 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 hover:bg-indigo-500 active:bg-gray-100 active:text-gray-700 last:rounded-r-md; -} - -.button-md svg, -button.input-action svg, -.plex-button svg { - @apply w-5 h-5 ml-2 mr-2 first:ml-0 last:mr-0; -} - -.button-sm svg { - @apply w-4 h-4 ml-1.5 mr-1.5 first:ml-0 last:mr-0; -} - -.modal-icon { - @apply flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto text-white bg-gray-800 rounded-full ring-1 ring-gray-500 sm:mx-0 sm:h-10 sm:w-10; -} - -.modal-icon svg { - @apply w-6 h-6; -} - -svg.icon-md { - @apply w-5 h-5; -} - -svg.icon-sm { - @apply w-4 h-4; -} - -.protocol { - @apply inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm; -} - -.error { - @apply mt-2 text-sm text-red-500; -} - -.form-group { - @apply mt-6 text-white; -} - -.toast { - width: 360px; -} - -/* Used for animating height */ -.extra-max-height { - max-height: 100rem; -} - -.hide-scrollbar { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ -} - -.hide-scrollbar::-webkit-scrollbar { - display: none; -} - -/* Hide scrollbar for Chrome, Safari and Opera */ -.hide-scrollbar::-webkit-scrollbar { - display: none; -} - -/* Hide scrollbar for IE, Edge and Firefox */ -.hide-scrollbar { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ -} - -code { - @apply px-2 py-1 bg-gray-800 rounded-md; -} - -input[type='search']::-webkit-search-cancel-button { - -webkit-appearance: none; -} - -.react-select-container { - @apply w-full; -} - -.react-select-container .react-select__control { - @apply text-white bg-gray-700 border border-gray-500 rounded-md hover:border-gray-500; -} - -.react-select-container-dark .react-select__control { - @apply bg-gray-800 border border-gray-700; -} - -.react-select-container .react-select__control--is-focused { - @apply text-white bg-gray-700 border border-gray-500 rounded-md shadow; -} - -.react-select-container-dark .react-select__control--is-focused { - @apply bg-gray-800 border-gray-600; -} - -.react-select-container .react-select__menu { - @apply text-gray-300 bg-gray-700; -} - -.react-select-container-dark .react-select__menu { - @apply bg-gray-800; -} - -.react-select-container .react-select__option--is-focused { - @apply text-white bg-gray-600; -} - -.react-select-container-dark .react-select__option--is-focused { - @apply bg-gray-700; -} - -.react-select-container .react-select__indicator-separator { - @apply bg-gray-500; -} - -.react-select-container .react-select__indicator { - @apply text-gray-500; -} - -.react-select-container .react-select__placeholder { - @apply text-gray-400; -} - -.react-select-container .react-select__multi-value { - @apply bg-gray-800 border border-gray-500 rounded-md; -} - -.react-select-container .react-select__multi-value__label { - @apply text-white; -} - -.react-select-container .react-select__multi-value__remove { - @apply cursor-pointer rounded-r-md hover:bg-red-700 hover:text-red-100; -} - -.react-select-container .react-select__input { - @apply text-base text-white border-none shadow-sm; -} - -.react-select-container .react-select__input input:focus { - @apply text-white border-none; - box-shadow: none; -} - -@media all and (display-mode: browser) { - .pwa-only { - @apply hidden; +@layer base { + html { + min-height: calc(100% + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) env(safe-area-inset-right) + env(safe-area-inset-bottom) env(safe-area-inset-left); + } + + body { + @apply bg-gray-900; + } + + code { + @apply px-2 py-1 bg-gray-800 rounded-md; + } + + input[type='search']::-webkit-search-cancel-button { + -webkit-appearance: none; + } +} + +@layer components { + .searchbar { + padding-top: env(safe-area-inset-top); + height: calc(4rem + env(safe-area-inset-top)); + } + + .sidebar { + @apply border-r border-gray-700; + padding-top: env(safe-area-inset-top); + padding-left: env(safe-area-inset-left); + background: linear-gradient(180deg, rgba(31, 41, 55, 1) 0%, #131928 100%); + } + + .slideover { + padding-top: calc(1.5rem + env(safe-area-inset-top)); + padding-bottom: 1.5rem; + } + + .sidebar-close-button { + top: env(safe-area-inset-top); + } + + .plex-button { + @apply flex justify-center w-full px-4 py-2 text-sm font-medium text-center text-white transition duration-150 ease-in-out bg-indigo-600 border border-transparent rounded-md disabled:opacity-50; + background-color: #cc7b19; + } + + .plex-button:hover { + background: #f19a30; + } + + ul.cards-vertical, + ul.cards-horizontal { + @apply grid gap-4; + } + + ul.cards-vertical { + grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr)); + } + + ul.cards-horizontal { + grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr)); + } + + .slider-header { + @apply relative flex mt-6 mb-4; + } + + .slider-title { + @apply inline-flex items-center text-xl font-bold leading-7 text-gray-300 sm:text-2xl sm:leading-9 sm:truncate; + } + + a.slider-title { + @apply transition duration-300 hover:text-white; + } + + a.slider-title svg { + @apply w-6 h-6 ml-2; + } + + .media-page { + @apply relative px-4 -mx-4 bg-center bg-cover; + margin-top: calc(-4rem - env(safe-area-inset-top)); + padding-top: calc(4rem + env(safe-area-inset-top)); + } + + .media-page-bg-image { + @apply absolute inset-0 w-full h-full; + z-index: -10; + } + + .media-header { + @apply flex flex-col items-center pt-4 xl:flex-row xl:items-end; + } + + .media-poster { + @apply w-32 overflow-hidden rounded shadow md:rounded-lg md:shadow-2xl md:w-44 xl:w-52 xl:mr-4; + } + + .media-status { + @apply mb-2 space-x-2; + } + + .media-title { + @apply flex flex-col flex-1 mt-4 text-center text-white xl:mr-4 xl:mt-0 xl:text-left; + } + + .media-title > h1 { + @apply text-2xl font-bold xl:text-4xl; + } + + h1 .media-year { + @apply text-2xl; + } + + .media-attributes { + @apply flex flex-wrap items-center justify-center mt-1 space-x-1 text-xs text-gray-300 sm:text-sm xl:text-base xl:mt-0 xl:justify-start; + } + + .media-attributes a { + @apply transition duration-300 hover:text-white hover:underline; + } + + .media-actions { + @apply relative flex flex-wrap items-center justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap xl:mt-0; + } + + .media-actions > * { + @apply mb-3 sm:mb-0; + } + + .media-overview { + @apply flex flex-col pt-8 pb-4 text-white lg:flex-row; + } + + .media-overview-left { + @apply flex-1 lg:mr-8; + } + + .tagline { + @apply mb-4 text-xl italic text-gray-400 lg:text-2xl; + } + + .media-overview h2 { + @apply text-xl font-bold text-gray-300 sm:text-2xl; + } + + .media-overview p { + @apply pt-2 text-sm text-gray-400 sm:text-base; + } + + ul.media-crew { + @apply grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3; + } + + ul.media-crew > li { + @apply flex flex-col col-span-1 font-bold text-gray-300; + } + + a.crew-name, + .media-fact-value a, + .media-fact-value button { + @apply font-normal text-gray-400 transition duration-300 hover:underline hover:text-gray-100; + } + + .media-overview-right { + @apply w-full mt-8 lg:w-80 lg:mt-0; + } + + .media-facts { + @apply text-sm font-bold text-gray-300 bg-gray-900 border border-gray-700 rounded-lg shadow; + } + + .media-fact { + @apply flex justify-between px-4 py-2 border-b border-gray-700 last:border-b-0; + } + + .media-fact-value { + @apply ml-2 text-sm font-normal text-right text-gray-400; + } + + .media-ratings { + @apply flex items-center justify-center px-4 py-2 font-medium border-b border-gray-700 last:border-b-0; + } + + .media-rating { + @apply flex items-center mr-4 last:mr-0; + } + + .error-message { + @apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300; + } + + .heading { + @apply text-2xl font-bold leading-8 text-gray-100; + } + + .description { + @apply max-w-4xl mt-1 text-sm leading-5 text-gray-400; + } + + img.avatar-sm { + @apply w-5 h-5 mr-1 transition duration-300 scale-100 rounded-full transform-gpu group-hover:scale-105; + } + + .card-field { + @apply flex items-center py-0.5 sm:py-1 text-sm truncate; + } + + .card-field-name { + @apply mr-2 font-bold; + } + + .card-field a { + @apply transition duration-300 hover:text-white hover:underline; + } + + .section { + @apply mt-6 mb-10 text-white; + } + + .form-row { + @apply max-w-6xl mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start; + } + + .form-input { + @apply text-sm text-white sm:col-span-2; + } + + .form-input-field { + @apply flex max-w-xl rounded-md shadow-sm; + } + + .actions { + @apply pt-5 mt-8 text-white border-t border-gray-700; + } + + label, + .group-label { + @apply block mb-1 text-sm font-bold leading-5 text-gray-400; + } + + label.checkbox-label { + @apply sm:mt-1; + } + + label.text-label { + @apply sm:mt-2; + } + + label a { + @apply text-gray-100 transition duration-300 hover:text-white hover:underline; + } + + .label-required { + @apply ml-1 text-red-500; + } + + .label-tip { + @apply block font-medium text-gray-500; + } + + button, + input, + select, + textarea { + @apply disabled:cursor-not-allowed; + } + + input[type='checkbox'] { + @apply w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md; + } + + input[type='text'], + input[type='password'], + select, + textarea { + @apply flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md sm:text-sm sm:leading-5; + } + + input.rounded-l-only, + select.rounded-l-only, + textarea.rounded-l-only { + @apply rounded-r-none; + } + + input.rounded-r-only, + select.rounded-r-only, + textarea.rounded-r-only { + @apply rounded-l-none; + } + + input.short { + @apply w-20; + } + + select.short { + @apply w-min; + } + + button > span { + @apply whitespace-nowrap; + } + + button.input-action { + @apply relative inline-flex items-center px-3 sm:px-3.5 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 hover:bg-indigo-500 active:bg-gray-100 active:text-gray-700 last:rounded-r-md; + } + + .button-md svg, + button.input-action svg, + .plex-button svg { + @apply w-5 h-5 ml-2 mr-2 first:ml-0 last:mr-0; + } + + .button-sm svg { + @apply w-4 h-4 ml-1.5 mr-1.5 first:ml-0 last:mr-0; + } + + .modal-icon { + @apply flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto text-white bg-gray-800 rounded-full ring-1 ring-gray-500 sm:mx-0 sm:h-10 sm:w-10; + } + + .modal-icon svg { + @apply w-6 h-6; + } + + svg.icon-md { + @apply w-5 h-5; + } + + svg.icon-sm { + @apply w-4 h-4; + } + + .protocol { + @apply inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm; + } + + .error { + @apply mt-2 text-sm text-red-500; + } + + .form-group { + @apply mt-6 text-white; + } + + .toast { + width: 360px; + } + + .react-select-container { + @apply w-full; + } + + .react-select-container .react-select__control { + @apply text-white bg-gray-700 border border-gray-500 rounded-md hover:border-gray-500; + } + + .react-select-container-dark .react-select__control { + @apply bg-gray-800 border border-gray-700; + } + + .react-select-container .react-select__control--is-focused { + @apply text-white bg-gray-700 border border-gray-500 rounded-md shadow; + } + + .react-select-container-dark .react-select__control--is-focused { + @apply bg-gray-800 border-gray-600; + } + + .react-select-container .react-select__menu { + @apply text-gray-300 bg-gray-700; + } + + .react-select-container-dark .react-select__menu { + @apply bg-gray-800; + } + + .react-select-container .react-select__option--is-focused { + @apply text-white bg-gray-600; + } + + .react-select-container-dark .react-select__option--is-focused { + @apply bg-gray-700; + } + + .react-select-container .react-select__indicator-separator { + @apply bg-gray-500; + } + + .react-select-container .react-select__indicator { + @apply text-gray-500; + } + + .react-select-container .react-select__placeholder { + @apply text-gray-400; + } + + .react-select-container .react-select__multi-value { + @apply bg-gray-800 border border-gray-500 rounded-md; + } + + .react-select-container .react-select__multi-value__label { + @apply text-white; + } + + .react-select-container .react-select__multi-value__remove { + @apply cursor-pointer rounded-r-md hover:bg-red-700 hover:text-red-100; + } + + .react-select-container .react-select__input { + @apply text-base text-white border-none shadow-sm; + } + + .react-select-container .react-select__input input:focus { + @apply text-white border-none; + box-shadow: none; + } +} + +@layer utilities { + .absolute-top-shift { + top: calc(-4rem - env(safe-area-inset-top)); + } + + .min-h-screen-shift { + min-height: calc(100vh + env(safe-area-inset-top)); + } + + /* Used for animating height */ + .extra-max-height { + max-height: 100rem; + } + + .hide-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + .hide-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for Chrome, Safari and Opera */ + .hide-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + .hide-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + @media all and (display-mode: browser) { + .pwa-only { + @apply hidden; + } } } diff --git a/stylelint.config.js b/stylelint.config.js index 79a284598..7339dadef 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -10,6 +10,7 @@ module.exports = { 'variants', 'responsive', 'screen', + 'layer', ], }, ],