mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-31 19:59:31 -05:00
feat: implement import users from Jellyfin button
This commit is contained in:
@@ -1984,6 +1984,20 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JellyfinLibrary'
|
||||
/settings/jellyfin/users:
|
||||
get:
|
||||
summary: Get Jellyfin Users
|
||||
description: Returns a list of Jellyfin Users in a JSON array.
|
||||
tags:
|
||||
- settings
|
||||
- users
|
||||
responses:
|
||||
'200':
|
||||
description: Jellyfin users returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
/settings/jellyfin/sync:
|
||||
get:
|
||||
summary: Get status of full Jellyfin library sync
|
||||
@@ -3528,6 +3542,35 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
/user/import-from-jellyfin:
|
||||
post:
|
||||
summary: Import all users from Jellyfin
|
||||
description: |
|
||||
Fetches and imports users from the Jellyfin server.
|
||||
|
||||
Requires the `MANAGE_USERS` permission.
|
||||
tags:
|
||||
- users
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
jellyfinIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: A list of the newly created users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
/user/registerPushSubscription:
|
||||
post:
|
||||
summary: Register a web push /user/registerPushSubscription
|
||||
|
||||
@@ -15,6 +15,10 @@ export interface JellyfinLoginResponse {
|
||||
AccessToken: string;
|
||||
}
|
||||
|
||||
export interface JellyfinUserListResponse {
|
||||
users: Array<JellyfinUserResponse>;
|
||||
}
|
||||
|
||||
export interface JellyfinLibrary {
|
||||
type: 'show' | 'movie';
|
||||
key: string;
|
||||
@@ -134,6 +138,19 @@ class JellyfinAPI {
|
||||
}
|
||||
}
|
||||
|
||||
public async getUsers(): Promise<JellyfinUserListResponse> {
|
||||
try {
|
||||
const account = await this.axios.get(`/Users`);
|
||||
return { users: account.data };
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Something went wrong while getting the account from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API' }
|
||||
);
|
||||
throw new Error('Invalid auth token');
|
||||
}
|
||||
}
|
||||
|
||||
public async getUser(): Promise<JellyfinUserResponse> {
|
||||
try {
|
||||
const account = await this.axios.get<JellyfinUserResponse>(
|
||||
|
||||
@@ -301,6 +301,34 @@ settingsRoutes.get('/jellyfin/library', async (req, res) => {
|
||||
return res.status(200).json(settings.jellyfin.libraries);
|
||||
});
|
||||
|
||||
settingsRoutes.get('/jellyfin/users', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
select: ['id', 'jellyfinAuthToken', 'jellyfinDeviceId', 'jellyfinUserId'],
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
const jellyfinClient = new JellyfinAPI(
|
||||
settings.jellyfin.hostname ?? '',
|
||||
admin.jellyfinAuthToken ?? '',
|
||||
admin.jellyfinDeviceId ?? ''
|
||||
);
|
||||
|
||||
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
|
||||
const resp = await jellyfinClient.getUsers();
|
||||
const users = resp.users.map((user) => ({
|
||||
username: user.Name,
|
||||
id: user.Id,
|
||||
thumb: user.PrimaryImageTag
|
||||
? `${settings.jellyfin.hostname}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90`
|
||||
: '/os_logo_square.png',
|
||||
email: user.Name,
|
||||
}));
|
||||
|
||||
return res.status(200).json(users);
|
||||
});
|
||||
|
||||
settingsRoutes.get('/jellyfin/sync', (_req, res) => {
|
||||
return res.status(200).json(jobJellyfinFullSync.status());
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Router } from 'express';
|
||||
import gravatarUrl from 'gravatar-url';
|
||||
import { findIndex, sortBy } from 'lodash';
|
||||
import { getRepository, In, Not } from 'typeorm';
|
||||
import JellyfinAPI from '../../api/jellyfin';
|
||||
import PlexTvAPI from '../../api/plextv';
|
||||
import TautulliAPI from '../../api/tautulli';
|
||||
import { MediaType } from '../../constants/media';
|
||||
@@ -465,6 +466,86 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/import-from-jellyfin',
|
||||
isAuthenticated(Permission.MANAGE_USERS),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const settings = getSettings();
|
||||
const userRepository = getRepository(User);
|
||||
const body = req.body as { jellyfinUserIds: string[] };
|
||||
|
||||
// taken from auth.ts
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
select: [
|
||||
'id',
|
||||
'jellyfinAuthToken',
|
||||
'jellyfinDeviceId',
|
||||
'jellyfinUserId',
|
||||
],
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
const jellyfinClient = new JellyfinAPI(
|
||||
settings.jellyfin.hostname ?? '',
|
||||
admin.jellyfinAuthToken ?? '',
|
||||
admin.jellyfinDeviceId ?? ''
|
||||
);
|
||||
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
|
||||
|
||||
const jellyfinUsersResponse = await jellyfinClient.getUsers();
|
||||
const createdUsers: User[] = [];
|
||||
for (const account of jellyfinUsersResponse.users) {
|
||||
if (account.Name) {
|
||||
const user = await userRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.jellyfinUserId = :id', { id: account.Id })
|
||||
.orWhere('user.email = :email', {
|
||||
email: account.Name,
|
||||
})
|
||||
.getOne();
|
||||
|
||||
const avatar = account.PrimaryImageTag
|
||||
? `${settings.jellyfin.hostname}/Users/${account.Id}/Images/Primary/?tag=${account.PrimaryImageTag}&quality=90`
|
||||
: '/os_logo_square.png';
|
||||
|
||||
if (user) {
|
||||
// Update the user's avatar with their Jellyfin thumbnail, in case it changed
|
||||
user.avatar = avatar;
|
||||
user.email = account.Name;
|
||||
user.jellyfinUsername = account.Name;
|
||||
|
||||
// In case the user was previously a local account
|
||||
if (user.userType === UserType.LOCAL) {
|
||||
user.userType = UserType.JELLYFIN;
|
||||
user.jellyfinUserId = account.Id;
|
||||
}
|
||||
await userRepository.save(user);
|
||||
} else if (!body || body.jellyfinUserIds.includes(account.Id)) {
|
||||
logger.error('CREATED USER', {
|
||||
label: 'API',
|
||||
});
|
||||
|
||||
const newUser = new User({
|
||||
jellyfinUsername: account.Name,
|
||||
jellyfinUserId: account.Id,
|
||||
email: account.Name,
|
||||
permissions: settings.main.defaultPermissions,
|
||||
avatar,
|
||||
userType: UserType.JELLYFIN,
|
||||
});
|
||||
await userRepository.save(newUser);
|
||||
createdUsers.push(newUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(201).json(User.filterMany(createdUsers));
|
||||
} catch (e) {
|
||||
next({ status: 500, message: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get<{ id: string }, QuotaResponse>(
|
||||
'/:id/quota',
|
||||
async (req, res, next) => {
|
||||
|
||||
251
src/components/UserList/JellyfinImportModal.tsx
Normal file
251
src/components/UserList/JellyfinImportModal.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import { InboxInIcon } from '@heroicons/react/solid';
|
||||
import axios from 'axios';
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import Alert from '../Common/Alert';
|
||||
import Modal from '../Common/Modal';
|
||||
|
||||
interface JellyfinImportProps {
|
||||
onCancel?: () => void;
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
importfromJellyfin: 'Import Jellyfin Users',
|
||||
importfromJellyfinerror:
|
||||
'Something went wrong while importing Jellyfin users.',
|
||||
importedfromJellyfin:
|
||||
'<strong>{userCount}</strong> Jellyfin {userCount, plural, one {user} other {users}} imported successfully!',
|
||||
user: 'User',
|
||||
noJellyfinuserstoimport: 'There are no Jellyfin users to import.',
|
||||
newJellyfinsigninenabled:
|
||||
'The <strong>Enable New Jellyfin Sign-In</strong> setting is currently enabled. Jellyfin users with library access do not need to be imported in order to sign in.',
|
||||
});
|
||||
|
||||
const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
onCancel,
|
||||
onComplete,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { addToast } = useToasts();
|
||||
const [isImporting, setImporting] = useState(false);
|
||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
||||
const { data, error } = useSWR<
|
||||
{
|
||||
id: string;
|
||||
title: string;
|
||||
username: string;
|
||||
email: string;
|
||||
thumb: string;
|
||||
}[]
|
||||
>(`/api/v1/settings/jellyfin/users`, {
|
||||
revalidateOnMount: true,
|
||||
});
|
||||
|
||||
const importUsers = async () => {
|
||||
setImporting(true);
|
||||
|
||||
try {
|
||||
const { data: createdUsers } = await axios.post(
|
||||
'/api/v1/user/import-from-jellyfin',
|
||||
{ jellyfinUserIds: selectedUsers }
|
||||
);
|
||||
|
||||
if (!createdUsers.length) {
|
||||
throw new Error('No users were imported from Jellyfin.');
|
||||
}
|
||||
|
||||
addToast(
|
||||
intl.formatMessage(messages.importedfromJellyfin, {
|
||||
userCount: createdUsers.length,
|
||||
strong: function strong(msg) {
|
||||
return <strong>{msg}</strong>;
|
||||
},
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'success',
|
||||
}
|
||||
);
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
} catch (e) {
|
||||
addToast(intl.formatMessage(messages.importfromJellyfinerror), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isSelectedUser = (JellyfinId: string): boolean =>
|
||||
selectedUsers.includes(JellyfinId);
|
||||
|
||||
const isAllUsers = (): boolean => selectedUsers.length === data?.length;
|
||||
|
||||
const toggleUser = (JellyfinId: string): void => {
|
||||
if (selectedUsers.includes(JellyfinId)) {
|
||||
setSelectedUsers((users) => users.filter((user) => user !== JellyfinId));
|
||||
} else {
|
||||
setSelectedUsers((users) => [...users, JellyfinId]);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleAllUsers = (): void => {
|
||||
if (data && selectedUsers.length >= 0 && !isAllUsers()) {
|
||||
setSelectedUsers(data.map((user) => user.id));
|
||||
} else {
|
||||
setSelectedUsers([]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
loading={!data && !error}
|
||||
title={intl.formatMessage(messages.importfromJellyfin)}
|
||||
iconSvg={<InboxInIcon />}
|
||||
onOk={() => {
|
||||
importUsers();
|
||||
}}
|
||||
okDisabled={isImporting || !selectedUsers.length}
|
||||
okText={intl.formatMessage(
|
||||
isImporting ? globalMessages.importing : globalMessages.import
|
||||
)}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
{data?.length ? (
|
||||
<>
|
||||
{settings.currentSettings.newPlexLogin && (
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.newJellyfinsigninenabled, {
|
||||
strong: function strong(msg) {
|
||||
return (
|
||||
<strong className="font-semibold text-white">{msg}</strong>
|
||||
);
|
||||
},
|
||||
})}
|
||||
type="info"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<div className="-mx-4 sm:mx-0">
|
||||
<div className="inline-block min-w-full py-2 align-middle">
|
||||
<div className="overflow-hidden shadow sm:rounded-lg">
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-16 bg-gray-500 px-4 py-3">
|
||||
<span
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
aria-checked={isAllUsers()}
|
||||
onClick={() => toggleAllUsers()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === 'Space') {
|
||||
toggleAllUsers();
|
||||
}
|
||||
}}
|
||||
className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
isAllUsers() ? 'bg-indigo-500' : 'bg-gray-800'
|
||||
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
isAllUsers() ? 'translate-x-5' : 'translate-x-0'
|
||||
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
|
||||
></span>
|
||||
</span>
|
||||
</th>
|
||||
<th className="bg-gray-500 px-1 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
|
||||
{intl.formatMessage(messages.user)}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700 bg-gray-600">
|
||||
{data?.map((user) => (
|
||||
<tr key={`user-${user.id}`}>
|
||||
<td className="whitespace-nowrap px-4 py-4 text-sm font-medium leading-5 text-gray-100">
|
||||
<span
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
aria-checked={isSelectedUser(user.id)}
|
||||
onClick={() => toggleUser(user.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === 'Space') {
|
||||
toggleUser(user.id);
|
||||
}
|
||||
}}
|
||||
className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
isSelectedUser(user.id)
|
||||
? 'bg-indigo-500'
|
||||
: 'bg-gray-800'
|
||||
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
isSelectedUser(user.id)
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0'
|
||||
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
|
||||
></span>
|
||||
</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
className="h-10 w-10 flex-shrink-0 rounded-full"
|
||||
src={user.thumb}
|
||||
alt=""
|
||||
/>
|
||||
<div className="ml-4">
|
||||
<div className="text-base font-bold leading-5">
|
||||
{user.username}
|
||||
</div>
|
||||
{/* {user.username &&
|
||||
user.username.toLowerCase() !==
|
||||
user.email && (
|
||||
<div className="text-sm leading-5 text-gray-300">
|
||||
{user.email}
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.noJellyfinuserstoimport)}
|
||||
type="info"
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default JellyfinImportModal;
|
||||
@@ -34,12 +34,13 @@ import SensitiveInput from '../Common/SensitiveInput';
|
||||
import Table from '../Common/Table';
|
||||
import Transition from '../Transition';
|
||||
import BulkEditModal from './BulkEditModal';
|
||||
import JellyfinImportModal from './JellyfinImportModal';
|
||||
import PlexImportModal from './PlexImportModal';
|
||||
|
||||
const messages = defineMessages({
|
||||
users: 'Users',
|
||||
userlist: 'User List',
|
||||
importfromplex: 'Import Plex Users',
|
||||
importfromplex: 'Import {mediaServerName} Users',
|
||||
user: 'User',
|
||||
totalrequests: 'Requests',
|
||||
accounttype: 'Type',
|
||||
@@ -464,6 +465,7 @@ const UserList: React.FC = () => {
|
||||
leaveTo="opacity-0"
|
||||
show={showImportModal}
|
||||
>
|
||||
{settings.currentSettings.mediaServerType === MediaServerType.PLEX ? (
|
||||
<PlexImportModal
|
||||
onCancel={() => setShowImportModal(false)}
|
||||
onComplete={() => {
|
||||
@@ -471,6 +473,15 @@ const UserList: React.FC = () => {
|
||||
revalidate();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<JellyfinImportModal
|
||||
onCancel={() => setShowImportModal(false)}
|
||||
onComplete={() => {
|
||||
setShowImportModal(false);
|
||||
revalidate();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
<div className="flex flex-col justify-between lg:flex-row lg:items-end">
|
||||
@@ -489,13 +500,17 @@ const UserList: React.FC = () => {
|
||||
className="flex-grow lg:mr-2"
|
||||
buttonType="primary"
|
||||
onClick={() => setShowImportModal(true)}
|
||||
disabled={
|
||||
settings.currentSettings.mediaServerType !==
|
||||
MediaServerType.PLEX
|
||||
}
|
||||
>
|
||||
<InboxInIcon />
|
||||
<span>{intl.formatMessage(messages.importfromplex)}</span>
|
||||
<span>
|
||||
{intl.formatMessage(messages.importfromplex, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: 'Jellyfin',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-2 flex flex-grow lg:mb-0 lg:flex-grow-0">
|
||||
|
||||
@@ -686,7 +686,7 @@
|
||||
"components.UserList.nouserstoimport": "No hi ha usuaris nous de Plex a importar.",
|
||||
"components.UserList.localuser": "Usuari local",
|
||||
"components.UserList.importfromplexerror": "S'ha produït un error en importar usuaris de Plex.",
|
||||
"components.UserList.importfromplex": "Importeu usuaris de Plex",
|
||||
"components.UserList.importfromplex": "Importeu usuaris de {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {usuari} other {usuaris}} de Plex importat correctament!",
|
||||
"components.TvDetails.watchtrailer": "Veure el tràiler",
|
||||
"components.TvDetails.viewfullcrew": "Mostreu equip complet",
|
||||
|
||||
@@ -850,7 +850,7 @@
|
||||
"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.importfromplex": "Importér Brugere fra {mediaServerName}",
|
||||
"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.",
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
"components.Settings.SettingsAbout.Releases.latestversion": "Neuste",
|
||||
"components.Settings.SettingsAbout.Releases.currentversion": "Aktuell",
|
||||
"components.UserList.importfromplexerror": "Beim Importieren von Plex-Benutzern ist etwas schief gelaufen.",
|
||||
"components.UserList.importfromplex": "Plex-Benutzer importieren",
|
||||
"components.UserList.importfromplex": "{mediaServerName}-Benutzer importieren",
|
||||
"components.TvDetails.viewfullcrew": "Komplette Crew anzeigen",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Komplette Serien-Crew",
|
||||
"components.PersonDetails.crewmember": "Crew",
|
||||
|
||||
@@ -602,7 +602,7 @@
|
||||
"components.UserList.localuser": "Τοπικός χρήστης",
|
||||
"components.UserList.localLoginDisabled": "Η ρύθμιση <strong>Ενεργοποίηση τοπικής σύνδεσης</strong> είναι προς το παρόν απενεργοποιημένη.",
|
||||
"components.UserList.importfromplexerror": "Κάτι πήγε στραβά κατά την εισαγωγή χρηστών από το Plex.",
|
||||
"components.UserList.importfromplex": "Εισαγωγή χρηστών από το Plex",
|
||||
"components.UserList.importfromplex": "Εισαγωγή χρηστών από το {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "{userCount, plural, one {# νέου χρήστη} other {#νέοι χρήστες}} εισήχθησαν απο το Plex επιτυχώς!",
|
||||
"components.UserList.email": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
|
||||
"components.UserList.edituser": "Επεξεργασία δικαιωμάτων χρήστη",
|
||||
|
||||
@@ -860,7 +860,7 @@
|
||||
"components.UserList.edituser": "Edit User Permissions",
|
||||
"components.UserList.email": "Email Address",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!",
|
||||
"components.UserList.importfromplex": "Import Plex Users",
|
||||
"components.UserList.importfromplex": "Import {mediaServerName} Users",
|
||||
"components.UserList.importfromplexerror": "Something went wrong while importing Plex users.",
|
||||
"components.UserList.localLoginDisabled": "The <strong>Enable Local Sign-In</strong> setting is currently disabled.",
|
||||
"components.UserList.localuser": "Local User",
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
"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",
|
||||
"components.UserList.importfromplex": "Importar Usuarios de {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "¡{userCount, plural, one {# nuevo usuario} other {# nuevos usuarios}} importado/s de Plex con éxito!",
|
||||
"components.TvDetails.viewfullcrew": "Ver Equipo Completo",
|
||||
"components.TvDetails.firstAirDate": "Primera fecha de emisión",
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
"components.Settings.SettingsAbout.Releases.latestversion": "Dernière version",
|
||||
"components.Settings.SettingsAbout.Releases.currentversion": "Actuelle",
|
||||
"components.UserList.importfromplexerror": "Une erreur s'est produite durant l'importation des utilisateurs de Plex.",
|
||||
"components.UserList.importfromplex": "Importer les utilisateurs de Plex",
|
||||
"components.UserList.importfromplex": "Importer les utilisateurs de {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {utilisateur} other {utilisateurs}} importé(s) depuis Plex avec succès !",
|
||||
"components.TvDetails.viewfullcrew": "Voir l'équipe complète de la série",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Équipe complète de la série",
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
"components.UserList.password": "Jelszó",
|
||||
"components.UserList.localuser": "Helyi felhasználó",
|
||||
"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.importfromplex": "Felhasználók importálása {mediaServerName}-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.deleteuser": "Felhasználó törlése",
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
"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 Plex.",
|
||||
"components.UserList.importfromplex": "Importa utenti Plex",
|
||||
"components.UserList.importfromplex": "Importa utenti {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {utente} other {utenti}} Plex {userCount, plural, one {importato} other {importati}} correttamente!",
|
||||
"components.TvDetails.viewfullcrew": "Vedi troupe completa",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Troupe completa serie",
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
"components.TvDetails.watchtrailer": "予告編を見る",
|
||||
"components.MovieDetails.watchtrailer": "予告編を見る",
|
||||
"components.UserList.importfromplexerror": "Plexからユーザーをインポート中に問題が発生しました。",
|
||||
"components.UserList.importfromplex": "Plexからユーザーをインポート",
|
||||
"components.UserList.importfromplex": "{mediaServerName}からユーザーをインポート",
|
||||
"components.UserList.importedfromplex": "Plex から新ユーザー {userCount} 名をインポートしました。",
|
||||
"components.TvDetails.viewfullcrew": "フルクルーを表示",
|
||||
"components.TvDetails.firstAirDate": "初放送日",
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
"components.UserList.userssaved": "Brukertillatelsene ble lagret!",
|
||||
"components.UserList.users": "Brukere",
|
||||
"components.UserList.importfromplexerror": "Noe gikk galt ved importering av brukere fra Plex.",
|
||||
"components.UserList.importfromplex": "Importer brukere fra Plex",
|
||||
"components.UserList.importfromplex": "Importer brukere fra {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {ny bruker} other {nye brukere}} ble importert fra Plex!",
|
||||
"components.Settings.menuUsers": "Brukere",
|
||||
"components.Settings.SettingsUsers.users": "Brukere",
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
"components.UserList.userdeleteerror": "Er ging iets mis bij het verwijderen van de gebruiker.",
|
||||
"components.UserList.userdeleted": "Gebruiker succesvol verwijderd!",
|
||||
"components.UserList.importfromplexerror": "Er is iets misgegaan bij het importeren van Plex-gebruikers.",
|
||||
"components.UserList.importfromplex": "Plex-gebruikers importeren",
|
||||
"components.UserList.importfromplex": "{mediaServerName}-gebruikers importeren",
|
||||
"components.UserList.deleteuser": "Gebruiker verwijderen",
|
||||
"components.UserList.deleteconfirm": "Weet je zeker dat je deze gebruiker wilt verwijderen? Al hun bestaande aanvraaggegevens zullen worden verwijderd.",
|
||||
"components.TvDetails.watchtrailer": "Trailer bekijken",
|
||||
|
||||
@@ -962,7 +962,7 @@
|
||||
"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": "<FindDiscordIdLink>Wielocyfrowy numer ID</FindDiscordIdLink> powiązany z Twoim kontem użytkownika",
|
||||
"components.UserList.importfromplex": "Importuj użytkowników Plex",
|
||||
"components.UserList.importfromplex": "Importuj użytkowników {mediaServerName}",
|
||||
"i18n.available": "Dostępny",
|
||||
"components.UserList.sortDisplayName": "Wyświetlana nazwa",
|
||||
"components.UserList.totalrequests": "Prośby",
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
"components.MovieDetails.viewfullcrew": "Ver Equipe Técnica Completa",
|
||||
"components.MovieDetails.MovieCrew.fullcrew": "Equipe Técnica Completa",
|
||||
"components.UserList.importfromplexerror": "Algo deu errado ao importar usuários do Plex.",
|
||||
"components.UserList.importfromplex": "Importar Usuários do Plex",
|
||||
"components.UserList.importfromplex": "Importar Usuários do {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {usuário Plex importado} other {usuários Plex importados}} com sucesso!",
|
||||
"components.Settings.Notifications.NotificationsSlack.agentenabled": "Habilitar Agente",
|
||||
"components.RequestList.RequestItem.failedretry": "Algo deu errado ao retentar fazer a solicitação.",
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
"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.importfromplexerror": "Ocorreu um erro ao importar utilizadores do Plex.",
|
||||
"components.UserList.importfromplex": "Importar Utilizadores do Plex",
|
||||
"components.UserList.importfromplex": "Importar Utilizadores do {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "{userCount, plural, one {# novo utilizador} other {# novos utilizadores}} importados do Plex com sucesso!",
|
||||
"components.UserList.email": "Endereço de E-mail",
|
||||
"components.UserList.deleteuser": "Apagar Utilizador",
|
||||
|
||||
@@ -793,7 +793,7 @@
|
||||
"components.UserList.usercreatedfailed": "Что-то пошло не так при создании пользователя.",
|
||||
"components.UserList.passwordinfodescription": "Настройте URL-адрес приложения и включите уведомления по электронной почте, чтобы обеспечить возможность автоматической генерации пароля.",
|
||||
"components.UserList.importfromplexerror": "Что-то пошло не так при импорте пользователей из Plex.",
|
||||
"components.UserList.importfromplex": "Импортировать пользователей из Plex",
|
||||
"components.UserList.importfromplex": "Импортировать пользователей из {mediaServerName}",
|
||||
"components.UserList.importedfromplex": "{userCount, plural, one {# новый пользователь} other {# новых пользователя(ей)}} успешно импортированы из Plex!",
|
||||
"components.UserList.edituser": "Изменить разрешения пользователя",
|
||||
"components.UserList.displayName": "Отображаемое имя",
|
||||
|
||||
@@ -1006,7 +1006,7 @@
|
||||
"components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Diçka shkoi keq duke ruajtur cilësimet.",
|
||||
"components.Settings.webAppUrlTip": "Në mënyrë opsionale drejto përdoruesit në aplikacionin web në serverin tënd në vend të atij web",
|
||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minuta",
|
||||
"components.UserList.importfromplex": "Importoni përdoruesit Plex",
|
||||
"components.UserList.importfromplex": "Importoni përdoruesit {mediaServerName}",
|
||||
"components.UserList.importfromplexerror": "Diçka shkoi keq duke importuar përdoruesit Plex.",
|
||||
"components.TvDetails.firstAirDate": "Data e parë e transmetimit",
|
||||
"components.UserList.email": "Adresa email",
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Versionsdata är för närvarande inte tillgänglig.",
|
||||
"components.Settings.SettingsAbout.Releases.latestversion": "Senaste Versionen",
|
||||
"components.Settings.SettingsAbout.Releases.currentversion": "Aktuell",
|
||||
"components.UserList.importfromplex": "Importera Plexanvändare",
|
||||
"components.UserList.importfromplex": "Importera {mediaServerName}användare",
|
||||
"components.UserList.importfromplexerror": "Något gick fel när Plexanvändare importerades.",
|
||||
"components.TvDetails.watchtrailer": "Kolla Trailer",
|
||||
"components.Settings.Notifications.allowselfsigned": "Tillåt Självsignerade Certifikat",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"components.UserList.localuser": "本地用户",
|
||||
"components.UserList.localLoginDisabled": "<strong>允许本地登录</strong>的设置目前被禁用。",
|
||||
"components.UserList.importfromplexerror": "导入 Plex 用户时出错。",
|
||||
"components.UserList.importfromplex": "导入 Plex 用户",
|
||||
"components.UserList.importfromplex": "导入 {mediaServerName} 用户",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} 成功导入!",
|
||||
"components.UserList.email": "电子邮件地址",
|
||||
"components.UserList.edituser": "编辑用户权限",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"components.Settings.radarrsettings": "Radarr 設定",
|
||||
"components.Settings.menuPlexSettings": "Plex",
|
||||
"components.UserList.importfromplexerror": "匯入 Plex 使用者時出了點問題。",
|
||||
"components.UserList.importfromplex": "匯入 Plex 使用者",
|
||||
"components.UserList.importfromplex": "匯入 {mediaServerName} 使用者",
|
||||
"components.UserList.importedfromplex": "匯入 <strong>{userCount}</strong> 個 Plex 使用者成功!",
|
||||
"components.UserList.localuser": "本地使用者",
|
||||
"components.UserList.creating": "創建中…",
|
||||
|
||||
Reference in New Issue
Block a user