diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2f2a91e8b0..5b30e77036 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -30,7 +30,7 @@ jobs: run: dotnet restore - name: Set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '17' @@ -107,9 +107,10 @@ jobs: if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }} steps: - name: Find Current Pull Request - uses: jwalton/gh-find-current-pr@v1.3.2 + uses: jwalton/gh-find-current-pr@v1 id: findPr with: + state: all github-token: ${{ secrets.GITHUB_TOKEN }} - name: Parse PR body @@ -130,7 +131,7 @@ jobs: body=${body//$'`'/'%60'} body=${body//$'>'/'%3E'} echo $body - echo "::set-output name=BODY::$body" + echo "BODY=$body" >> $GITHUB_OUTPUT - name: Check Out Repo uses: actions/checkout@v3 @@ -138,7 +139,7 @@ jobs: ref: develop - name: NodeJS to Compile WebUI - uses: actions/setup-node@v2.1.5 + uses: actions/setup-node@v3 with: node-version: '16' - run: | @@ -155,7 +156,7 @@ jobs: cd ../ || exit - name: Get csproj Version - uses: naminodarie/get-net-sdk-project-versions-action@v1 + uses: kzrnm/get-net-sdk-project-versions-action@v1 id: get-version with: proj-path: Kavita.Common/Kavita.Common.csproj @@ -163,7 +164,7 @@ jobs: - name: Parse Version run: | version='${{steps.get-version.outputs.assembly-version}}' - echo "::set-output name=VERSION::$version" + echo "VERSION=$version" >> $GITHUB_OUTPUT id: parse-version - name: Echo csproj version @@ -193,15 +194,15 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Build and push id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 @@ -216,7 +217,7 @@ jobs: with: severity: info description: v${{steps.get-version.outputs.assembly-version}} - ${{ steps.findPr.outputs.title }} - details: '${{ steps.parse-body.outputs.BODY }}' + details: '${{ steps.findPr.outputs.body }}' text: <@&939225459156217917> <@&939225350775406643> A new nightly build has been released for docker. webhookUrl: ${{ secrets.DISCORD_DOCKER_UPDATE_URL }} @@ -227,13 +228,14 @@ jobs: permissions: packages: write contents: read - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) }} steps: - name: Find Current Pull Request - uses: jwalton/gh-find-current-pr@v1.0.2 + uses: jwalton/gh-find-current-pr@v1 id: findPr with: + state: all github-token: ${{ secrets.GITHUB_TOKEN }} - name: Parse PR body @@ -254,7 +256,8 @@ jobs: body=${body//$'`'/'%60'} body=${body//$'>'/'%3E'} echo $body - echo "::set-output name=BODY::$body" + echo "BODY=$body" >> $GITHUB_OUTPUT + - name: Check Out Repo uses: actions/checkout@v3 @@ -262,7 +265,7 @@ jobs: ref: main - name: NodeJS to Compile WebUI - uses: actions/setup-node@v2.1.5 + uses: actions/setup-node@v3 with: node-version: '16' - run: | @@ -280,7 +283,7 @@ jobs: cd ../ || exit - name: Get csproj Version - uses: naminodarie/get-net-sdk-project-versions-action@v1 + uses: kzrnm/get-net-sdk-project-versions-action@v1 id: get-version with: proj-path: Kavita.Common/Kavita.Common.csproj @@ -293,7 +296,7 @@ jobs: version='${{steps.get-version.outputs.assembly-version}}' newVersion=${version%.*} echo $newVersion - echo "::set-output name=VERSION::$newVersion" + echo "VERSION=$newVersion" >> $GITHUB_OUTPUT id: parse-version - name: Compile dotnet app @@ -319,15 +322,15 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Build and push id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 @@ -342,7 +345,7 @@ jobs: with: severity: info description: v${{steps.get-version.outputs.assembly-version}} - ${{ steps.findPr.outputs.title }} - details: '${{ steps.parse-body.outputs.BODY }}' + details: '${{ steps.findPr.outputs.body }}' text: <@&939225192553644133> A new stable build has been released. webhookUrl: ${{ secrets.DISCORD_DOCKER_UPDATE_URL }} @@ -356,9 +359,10 @@ jobs: if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/canary' }} steps: - name: Find Current Pull Request - uses: jwalton/gh-find-current-pr@v1.0.2 + uses: jwalton/gh-find-current-pr@v1 id: findPr with: + state: all github-token: ${{ secrets.GITHUB_TOKEN }} - name: Check Out Repo @@ -367,7 +371,7 @@ jobs: ref: canary - name: NodeJS to Compile WebUI - uses: actions/setup-node@v2.1.5 + uses: actions/setup-node@v3 with: node-version: '16' - run: | @@ -384,7 +388,7 @@ jobs: cd ../ || exit - name: Get csproj Version - uses: naminodarie/get-net-sdk-project-versions-action@v1 + uses: kzrnm/get-net-sdk-project-versions-action@v1 id: get-version with: proj-path: Kavita.Common/Kavita.Common.csproj @@ -392,7 +396,7 @@ jobs: - name: Parse Version run: | version='${{steps.get-version.outputs.assembly-version}}' - echo "::set-output name=VERSION::$version" + echo "VERSION=$version" >> $GITHUB_OUTPUT id: parse-version - name: Echo csproj version @@ -422,15 +426,15 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Build and push id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 diff --git a/API.Tests/Extensions/SeriesFilterTests.cs b/API.Tests/Extensions/SeriesFilterTests.cs new file mode 100644 index 0000000000..2774ad78ef --- /dev/null +++ b/API.Tests/Extensions/SeriesFilterTests.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using API.DTOs.Filtering.v2; +using API.Extensions.QueryExtensions.Filtering; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace API.Tests.Extensions; + +public class SeriesFilterTests : AbstractDbTest +{ + + protected override Task ResetDb() + { + return Task.CompletedTask; + } + + #region HasLanguage + + [Fact] + public async Task HasLanguage_Works() + { + var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.Contains, new List() { }).ToListAsync(); + + } + + #endregion +} diff --git a/API.Tests/Services/CacheServiceTests.cs b/API.Tests/Services/CacheServiceTests.cs index f59afcf744..e1419e0526 100644 --- a/API.Tests/Services/CacheServiceTests.cs +++ b/API.Tests/Services/CacheServiceTests.cs @@ -42,7 +42,7 @@ public int GetNumberOfPages(string filePath, MangaFormat format) return 1; } - public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat) + public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default) { return string.Empty; } diff --git a/API.Tests/Services/ParseScannedFilesTests.cs b/API.Tests/Services/ParseScannedFilesTests.cs index 915e584ca8..32ad8f6450 100644 --- a/API.Tests/Services/ParseScannedFilesTests.cs +++ b/API.Tests/Services/ParseScannedFilesTests.cs @@ -43,7 +43,7 @@ public int GetNumberOfPages(string filePath, MangaFormat format) return 1; } - public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat) + public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default) { return string.Empty; } diff --git a/API.Tests/Services/SeriesServiceTests.cs b/API.Tests/Services/SeriesServiceTests.cs index 759577bc18..4a2ed0f32b 100644 --- a/API.Tests/Services/SeriesServiceTests.cs +++ b/API.Tests/Services/SeriesServiceTests.cs @@ -802,6 +802,26 @@ private static Series CreateSeriesMock() return series; } + [Fact] + public void GetFirstChapterForMetadata_BookWithOnlyVolumeNumbers_Test() + { + var file = new MangaFileBuilder("Test.cbz", MangaFormat.Archive, 1).Build(); + + var series = new SeriesBuilder("Test") + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("0").WithPages(1).WithFile(file).Build()) + .Build()) + + .WithVolume(new VolumeBuilder("1.5") + .WithChapter(new ChapterBuilder("0").WithPages(2).WithFile(file).Build()) + .Build()) + .Build(); + series.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); + + var firstChapter = SeriesService.GetFirstChapterForMetadata(series); + Assert.Equal(1, firstChapter.Pages); + } + [Fact] public void GetFirstChapterForMetadata_Book_Test() { diff --git a/API/Constants/CacheProfiles.cs b/API/Constants/CacheProfiles.cs index dd25f27a66..bf5414eba5 100644 --- a/API/Constants/CacheProfiles.cs +++ b/API/Constants/CacheProfiles.cs @@ -15,6 +15,10 @@ public static class EasyCacheProfiles /// Cache the libraries on the server /// public const string Library = "library"; + /// + /// Metadata filter + /// + public const string Filter = "filter"; public const string KavitaPlusReviews = "kavita+reviews"; public const string KavitaPlusRecommendations = "kavita+recommendations"; public const string KavitaPlusRatings = "kavita+ratings"; diff --git a/API/Controllers/CollectionController.cs b/API/Controllers/CollectionController.cs index 8d3abb3ad7..4f5f955be5 100644 --- a/API/Controllers/CollectionController.cs +++ b/API/Controllers/CollectionController.cs @@ -32,7 +32,7 @@ public CollectionController(IUnitOfWork unitOfWork, ICollectionTagService collec } /// - /// Return a list of all collection tags on the server + /// Return a list of all collection tags on the server for the logged in user. /// /// [HttpGet] @@ -130,7 +130,6 @@ public async Task RemoveTagFromMultipleSeries(UpdateSeriesForTagDt { var tag = await _unitOfWork.CollectionTagRepository.GetTagAsync(updateSeriesForTagDto.Tag.Id, CollectionTagIncludes.SeriesMetadata); if (tag == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "collection-doesnt-exist")); - tag.SeriesMetadatas ??= new List(); if (await _collectionService.RemoveTagFromSeries(tag, updateSeriesForTagDto.SeriesIdsToRemove)) return Ok(await _localizationService.Translate(User.GetUserId(), "collection-updated")); @@ -142,4 +141,29 @@ public async Task RemoveTagFromMultipleSeries(UpdateSeriesForTagDt return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); } + + /// + /// Removes the collection tag from all Series it was attached to + /// + /// + /// + [Authorize(Policy = "RequireAdminRole")] + [HttpDelete] + public async Task DeleteTag(int tagId) + { + try + { + var tag = await _unitOfWork.CollectionTagRepository.GetTagAsync(tagId, CollectionTagIncludes.SeriesMetadata); + if (tag == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "collection-doesnt-exist")); + + if (await _collectionService.DeleteTag(tag)) + return Ok(await _localizationService.Translate(User.GetUserId(), "collection-deleted")); + } + catch (Exception) + { + await _unitOfWork.RollbackAsync(); + } + + return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); + } } diff --git a/API/Controllers/DeviceController.cs b/API/Controllers/DeviceController.cs index 90b3723d74..fa5bc34fa7 100644 --- a/API/Controllers/DeviceController.cs +++ b/API/Controllers/DeviceController.cs @@ -87,8 +87,7 @@ public async Task DeleteDevice(int deviceId) [HttpGet] public async Task>> GetDevices() { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.DeviceRepository.GetDevicesForUserAsync(userId)); + return Ok(await _unitOfWork.DeviceRepository.GetDevicesForUserAsync(User.GetUserId())); } [HttpPost("send-to")] @@ -100,7 +99,7 @@ public async Task SendToDevice(SendToDeviceDto dto) if (await _emailService.IsDefaultEmailService()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email")); - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + var userId = User.GetUserId(); await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress, MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"), "started"), userId); @@ -134,7 +133,7 @@ public async Task SendSeriesToDevice(SendSeriesToDeviceDto dto) if (await _emailService.IsDefaultEmailService()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email")); - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + var userId = User.GetUserId(); await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress, MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"), "started"), userId); diff --git a/API/Controllers/FilterController.cs b/API/Controllers/FilterController.cs new file mode 100644 index 0000000000..7b6e41ef8f --- /dev/null +++ b/API/Controllers/FilterController.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using API.Constants; +using API.Data; +using API.DTOs.Filtering.v2; +using EasyCaching.Core; +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers; + +/// +/// This is responsible for Filter caching +/// +public class FilterController : BaseApiController +{ + private readonly IUnitOfWork _unitOfWork; + private readonly IEasyCachingProviderFactory _cacheFactory; + + public FilterController(IUnitOfWork unitOfWork, IEasyCachingProviderFactory cacheFactory) + { + _unitOfWork = unitOfWork; + _cacheFactory = cacheFactory; + } + + [HttpGet] + public async Task> GetFilter(string name) + { + var provider = _cacheFactory.GetCachingProvider(EasyCacheProfiles.Filter); + if (string.IsNullOrEmpty(name)) return Ok(null); + var filter = await provider.GetAsync(name); + if (filter.HasValue) + { + filter.Value.Name = name; + return Ok(filter.Value); + } + + return Ok(null); + } + + /// + /// Caches the filter in the backend and returns a temp string for retrieving. + /// + /// The cache line lives for only 1 hour + /// + /// + [HttpPost("create-temp")] + public async Task> CreateTempFilter(FilterV2Dto filterDto) + { + var provider = _cacheFactory.GetCachingProvider(EasyCacheProfiles.Filter); + var name = filterDto.Name; + if (string.IsNullOrEmpty(filterDto.Name)) + { + name = Guid.NewGuid().ToString(); + } + + await provider.SetAsync(name, filterDto, TimeSpan.FromHours(1)); + return name; + } +} diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs index bbb32e0425..8dcd3749d6 100644 --- a/API/Controllers/ImageController.cs +++ b/API/Controllers/ImageController.cs @@ -158,11 +158,6 @@ public async Task GetReadingListCoverImage(int readingListId, stri private async Task GenerateReadingListCoverImage(int readingListId) { var covers = await _unitOfWork.ReadingListRepository.GetRandomCoverImagesAsync(readingListId); - if (covers.Count < 4) - { - return string.Empty; - } - var destFile = _directoryService.FileSystem.Path.Join(_directoryService.TempDirectory, ImageService.GetReadingListFormat(readingListId)); var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); @@ -171,6 +166,7 @@ private async Task GenerateReadingListCoverImage(int readingListId) if (_directoryService.FileSystem.File.Exists(destFile)) return destFile; ImageService.CreateMergedImage( covers.Select(c => _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, c)).ToList(), + settings.CoverImageSize, destFile); return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile; } @@ -178,11 +174,6 @@ private async Task GenerateReadingListCoverImage(int readingListId) private async Task GenerateCollectionCoverImage(int collectionId) { var covers = await _unitOfWork.CollectionTagRepository.GetRandomCoverImagesAsync(collectionId); - if (covers.Count < 4) - { - return string.Empty; - } - var destFile = _directoryService.FileSystem.Path.Join(_directoryService.TempDirectory, ImageService.GetCollectionTagFormat(collectionId)); var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); @@ -190,6 +181,7 @@ private async Task GenerateCollectionCoverImage(int collectionId) if (_directoryService.FileSystem.File.Exists(destFile)) return destFile; ImageService.CreateMergedImage( covers.Select(c => _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, c)).ToList(), + settings.CoverImageSize, destFile); return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile; } diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index 55890c13e2..fc39655778 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -161,8 +161,7 @@ public async Task>> GetLibraries() [HttpGet("jump-bar")] public async Task>> GetJumpBar(int libraryId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - if (!await _unitOfWork.UserRepository.HasAccessToLibrary(libraryId, userId)) + if (!await _unitOfWork.UserRepository.HasAccessToLibrary(libraryId, User.GetUserId())) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-library-access")); return Ok(_unitOfWork.LibraryRepository.GetJumpBarAsync(libraryId)); diff --git a/API/Controllers/MetadataController.cs b/API/Controllers/MetadataController.cs index a5d4996ffa..0abf032af2 100644 --- a/API/Controllers/MetadataController.cs +++ b/API/Controllers/MetadataController.cs @@ -37,17 +37,28 @@ public MetadataController(IUnitOfWork unitOfWork, ILocalizationService localizat [ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})] public async Task>> GetAllGenres(string? libraryIds) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList(); if (ids != null && ids.Count > 0) { - return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids, userId)); + return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids, User.GetUserId())); } - return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosAsync(userId)); + return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosAsync(User.GetUserId())); } - + /// + /// Fetches people from the instance by role + /// + /// role + /// + [HttpGet("people-by-role")] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"role"})] + public async Task>> GetAllPeople(PersonRole? role) + { + return role.HasValue ? + Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosByRoleAsync(User.GetUserId(), role!.Value)) : + Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(User.GetUserId())); + } /// /// Fetches people from the instance @@ -58,13 +69,12 @@ public async Task>> GetAllGenres(string? library [ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})] public async Task>> GetAllPeople(string? libraryIds) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList(); if (ids != null && ids.Count > 0) { - return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids, userId)); + return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids, User.GetUserId())); } - return Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(userId)); + return Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(User.GetUserId())); } /// @@ -76,13 +86,12 @@ public async Task>> GetAllPeople(string? libraryId [ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})] public async Task>> GetAllTags(string? libraryIds) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList(); if (ids != null && ids.Count > 0) { - return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids, userId)); + return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids, User.GetUserId())); } - return Ok(await _unitOfWork.TagRepository.GetAllTagDtosAsync(userId)); + return Ok(await _unitOfWork.TagRepository.GetAllTagDtosAsync(User.GetUserId())); } /// @@ -138,19 +147,14 @@ public ActionResult> GetAllPublicationStatus(string? library /// String separated libraryIds or null for all ratings /// [HttpGet("languages")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute, VaryByQueryKeys = new []{"libraryIds"})] public async Task>> GetAllLanguages(string? libraryIds) { var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList(); - if (ids is {Count: > 0}) - { - return Ok(await _unitOfWork.LibraryRepository.GetAllLanguagesForLibrariesAsync(ids)); - } - - - return Ok(await _unitOfWork.LibraryRepository.GetAllLanguagesForLibrariesAsync()); + return Ok(await _unitOfWork.LibraryRepository.GetAllLanguagesForLibrariesAsync(ids)); } + [HttpGet("all-languages")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)] public IEnumerable GetAllValidLanguages() @@ -163,6 +167,7 @@ public IEnumerable GetAllValidLanguages() }).Where(l => !string.IsNullOrEmpty(l.IsoCode)); } + /// /// Returns summary for the chapter /// diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 814ce78433..79380a4ea3 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -10,6 +10,7 @@ using API.DTOs; using API.DTOs.CollectionTags; using API.DTOs.Filtering; +using API.DTOs.Filtering.v2; using API.DTOs.OPDS; using API.DTOs.Search; using API.Entities; @@ -65,6 +66,8 @@ public class OpdsController : BaseApiController SortOptions = null, PublicationStatus = new List() }; + + private readonly FilterV2Dto _filterV2Dto = new FilterV2Dto(); private readonly ChapterSortComparer _chapterSortComparer = ChapterSortComparer.Default; private const int PageSize = 20; @@ -139,6 +142,19 @@ public async Task Get(string apiKey) } }); feed.Entries.Add(new FeedEntry() + { + Id = "wantToRead", + Title = await _localizationService.Translate(userId, "want-to-read"), + Content = new FeedEntryContent() + { + Text = await _localizationService.Translate(userId, "browse-want-to-read") + }, + Links = new List() + { + CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/want-to-read"), + } + }); + feed.Entries.Add(new FeedEntry() { Id = "allLibraries", Title = await _localizationService.Translate(userId, "libraries"), @@ -201,6 +217,8 @@ public async Task GetLibraries(string apiKey) Links = new List() { CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/libraries/{library.Id}"), + CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/library-cover?libraryId={library.Id}&apiKey={apiKey}"), + CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/library-cover?libraryId={library.Id}&apiKey={apiKey}") } }); } @@ -208,6 +226,27 @@ public async Task GetLibraries(string apiKey) return CreateXmlResult(SerializeXml(feed)); } + [HttpGet("{apiKey}/want-to-read")] + [Produces("application/xml")] + public async Task GetWantToRead(string apiKey, [FromQuery] int pageNumber = 0) + { + var userId = await GetUser(apiKey); + if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) + return BadRequest(await _localizationService.Translate(userId, "opds-disabled")); + var (baseUrl, prefix) = await GetPrefix(); + var wantToReadSeries = await _unitOfWork.SeriesRepository.GetWantToReadForUserV2Async(userId, GetUserParams(pageNumber), _filterV2Dto); + var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(wantToReadSeries.Select(s => s.Id)); + + var feed = CreateFeed(await _localizationService.Translate(userId, "want-to-read"), $"{apiKey}/want-to-read", apiKey, prefix); + SetFeedId(feed, $"want-to-read"); + AddPagination(feed, wantToReadSeries, $"{prefix}{apiKey}/want-to-read"); + + feed.Entries.AddRange(wantToReadSeries.Select(seriesDto => + CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl))); + + return CreateXmlResult(SerializeXml(feed)); + } + [HttpGet("{apiKey}/collections")] [Produces("application/xml")] public async Task GetCollections(string apiKey) @@ -226,21 +265,19 @@ public async Task GetCollections(string apiKey) var feed = CreateFeed(await _localizationService.Translate(userId, "collections"), $"{prefix}{apiKey}/collections", apiKey, prefix); SetFeedId(feed, "collections"); - foreach (var tag in tags) + + feed.Entries.AddRange(tags.Select(tag => new FeedEntry() { - feed.Entries.Add(new FeedEntry() + Id = tag.Id.ToString(), + Title = tag.Title, + Summary = tag.Summary, + Links = new List() { - Id = tag.Id.ToString(), - Title = tag.Title, - Summary = tag.Summary, - Links = new List() - { - CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/collections/{tag.Id}"), - CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionId={tag.Id}&apiKey={apiKey}"), - CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionId={tag.Id}&apiKey={apiKey}") - } - }); - } + CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/collections/{tag.Id}"), + CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionTagId={tag.Id}&apiKey={apiKey}"), + CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionTagId={tag.Id}&apiKey={apiKey}") + } + })); return CreateXmlResult(SerializeXml(feed)); } @@ -315,6 +352,8 @@ public async Task GetReadingLists(string apiKey, [FromQuery] int Links = new List() { CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/reading-list/{readingListDto.Id}"), + CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/readinglist-cover?readingListId={readingListDto.Id}&apiKey={apiKey}"), + CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/readinglist-cover?readingListId={readingListDto.Id}&apiKey={apiKey}") } }); } @@ -378,17 +417,27 @@ public async Task GetSeriesForLibrary(int libraryId, string apiKe return BadRequest(await _localizationService.Translate(userId, "no-library-access")); } - var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, GetUserParams(pageNumber), _filterDto); + var filter = new FilterV2Dto + { + Statements = new List() { + new () + { + Comparison = FilterComparison.Equal, + Field = FilterField.Libraries, + Value = libraryId + string.Empty + } + } + }; + + var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, GetUserParams(pageNumber), filter); var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(series.Select(s => s.Id)); var feed = CreateFeed(library.Name, $"{apiKey}/libraries/{libraryId}", apiKey, prefix); SetFeedId(feed, $"library-{library.Name}"); AddPagination(feed, series, $"{prefix}{apiKey}/libraries/{libraryId}"); - foreach (var seriesDto in series) - { - feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl)); - } + feed.Entries.AddRange(series.Select(seriesDto => + CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl))); return CreateXmlResult(SerializeXml(feed)); } @@ -401,7 +450,7 @@ public async Task GetRecentlyAdded(string apiKey, [FromQuery] int if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) return BadRequest(await _localizationService.Translate(userId, "opds-disabled")); var (baseUrl, prefix) = await GetPrefix(); - var recentlyAdded = await _unitOfWork.SeriesRepository.GetRecentlyAdded(0, userId, GetUserParams(pageNumber), _filterDto); + var recentlyAdded = await _unitOfWork.SeriesRepository.GetRecentlyAddedV2(userId, GetUserParams(pageNumber), _filterV2Dto); var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(recentlyAdded.Select(s => s.Id)); var feed = CreateFeed(await _localizationService.Translate(userId, "recently-added"), $"{prefix}{apiKey}/recently-added", apiKey, prefix); @@ -730,8 +779,10 @@ private static FeedEntry CreateSeries(SeriesDto seriesDto, SeriesMetadataDto met return new FeedEntry() { Id = seriesDto.Id.ToString(), - Title = $"{seriesDto.Name} ({seriesDto.Format})", - Summary = seriesDto.Summary, + Title = $"{seriesDto.Name}", + Summary = $"Format: {seriesDto.Format}" + (string.IsNullOrWhiteSpace(metadata.Summary) + ? string.Empty + : $" Summary: {metadata.Summary}"), Authors = metadata.Writers.Select(p => new FeedAuthor() { Name = p.Name, @@ -756,7 +807,8 @@ private static FeedEntry CreateSeries(SearchResultDto searchResultDto, string ap return new FeedEntry() { Id = searchResultDto.SeriesId.ToString(), - Title = $"{searchResultDto.Name} ({searchResultDto.Format})", + Title = $"{searchResultDto.Name}", + Summary = $"Format: {searchResultDto.Format}", Links = new List() { CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/series/{searchResultDto.SeriesId}"), diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 57a0e00a45..39748325f2 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -8,6 +8,7 @@ using API.Data.Repositories; using API.DTOs; using API.DTOs.Filtering; +using API.DTOs.Filtering.v2; using API.DTOs.Reader; using API.Entities; using API.Entities.Enums; @@ -596,7 +597,7 @@ public async Task>> GetBookmarks(int chapt /// Only supports SeriesNameQuery /// [HttpPost("all-bookmarks")] - public async Task>> GetAllBookmarks(FilterDto filterDto) + public async Task>> GetAllBookmarks(FilterV2Dto filterDto) { return Ok(await _unitOfWork.UserRepository.GetAllBookmarkDtos(User.GetUserId(), filterDto)); } diff --git a/API/Controllers/ReadingListController.cs b/API/Controllers/ReadingListController.cs index 0e88997837..329fed1e21 100644 --- a/API/Controllers/ReadingListController.cs +++ b/API/Controllers/ReadingListController.cs @@ -39,8 +39,7 @@ public ReadingListController(IUnitOfWork unitOfWork, IReadingListService reading [HttpGet] public async Task>> GetList(int readingListId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, userId)); + return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, User.GetUserId())); } /// @@ -54,8 +53,7 @@ public async Task>> GetList(int reading public async Task>> GetListsForUser([FromQuery] UserParams userParams, bool includePromoted = true, bool sortByLastModified = false) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted, + var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(User.GetUserId(), includePromoted, userParams, sortByLastModified); Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages); @@ -70,10 +68,8 @@ public async Task>> GetListsForUser([Fr [HttpGet("lists-for-series")] public async Task>> GetListsForSeries(int seriesId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForSeriesAndUserAsync(userId, seriesId, true); - - return Ok(items); + return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtosForSeriesAndUserAsync(User.GetUserId(), + seriesId, true)); } /// diff --git a/API/Controllers/ReviewController.cs b/API/Controllers/ReviewController.cs index fa2d7b8433..e2424a6dc4 100644 --- a/API/Controllers/ReviewController.cs +++ b/API/Controllers/ReviewController.cs @@ -62,16 +62,18 @@ public async Task>> GetReviews(int serie } var cacheKey = CacheKey + seriesId; - IEnumerable externalReviews; + IList externalReviews; var result = await _cacheProvider.GetAsync>(cacheKey); if (result.HasValue) { - externalReviews = result.Value; + externalReviews = result.Value.ToList(); } else { - externalReviews = (await _reviewService.GetReviewsForSeries(userId, seriesId)).ToList(); + var reviews = (await _reviewService.GetReviewsForSeries(userId, seriesId)).ToList(); + externalReviews = SelectSpectrumOfReviews(reviews); + await _cacheProvider.SetAsync(cacheKey, externalReviews, TimeSpan.FromHours(10)); _logger.LogDebug("Caching external reviews for {Key}", cacheKey); } @@ -80,7 +82,44 @@ public async Task>> GetReviews(int serie // Fetch external reviews and splice them in userRatings.AddRange(externalReviews); - return Ok(userRatings.Take(10)); + + return Ok(userRatings); + } + + private static IList SelectSpectrumOfReviews(IList reviews) + { + IList externalReviews; + var totalReviews = reviews.Count; + + if (totalReviews > 10) + { + //var stepSize = Math.Max(totalReviews / 10, 1); // Calculate step size, ensuring it's at least 1 + var stepSize = Math.Max((totalReviews - 4) / 8, 1); + + var selectedReviews = new List() + { + reviews[0], + reviews[1], + }; + for (var i = 2; i < totalReviews - 2; i += stepSize) + { + selectedReviews.Add(reviews[i]); + + if (selectedReviews.Count >= 8) + break; + } + + selectedReviews.Add(reviews[totalReviews - 2]); + selectedReviews.Add(reviews[totalReviews - 1]); + + externalReviews = selectedReviews; + } + else + { + externalReviews = reviews; + } + + return externalReviews; } /// diff --git a/API/Controllers/ScrobblingController.cs b/API/Controllers/ScrobblingController.cs index caf49d6dbc..d268229543 100644 --- a/API/Controllers/ScrobblingController.cs +++ b/API/Controllers/ScrobblingController.cs @@ -143,8 +143,7 @@ public async Task> HasHold(int seriesId) [HttpGet("library-allows-scrobbling")] public async Task> LibraryAllowsScrobbling(int seriesId) { - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Library); - return Ok(series != null && series.Library.AllowScrobbling); + return Ok(await _unitOfWork.LibraryRepository.GetAllowsScrobblingBySeriesId(seriesId)); } /// diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index 03ba05bed3..98c9698003 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -33,8 +33,7 @@ public SearchController(IUnitOfWork unitOfWork, ILocalizationService localizatio [HttpGet("series-for-mangafile")] public async Task> GetSeriesForMangaFile(int mangaFileId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.SeriesRepository.GetSeriesForMangaFile(mangaFileId, userId)); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesForMangaFile(mangaFileId, User.GetUserId())); } /// @@ -46,8 +45,7 @@ public async Task> GetSeriesForMangaFile(int mangaFileId [HttpGet("series-for-chapter")] public async Task> GetSeriesForChapter(int chapterId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapterId, userId)); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapterId, User.GetUserId())); } [HttpGet("search")] diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index c420a955fd..a86d9626a0 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Constants; @@ -6,6 +7,7 @@ using API.Data.Repositories; using API.DTOs; using API.DTOs.Filtering; +using API.DTOs.Filtering.v2; using API.DTOs.Metadata; using API.DTOs.SeriesDetail; using API.Entities; @@ -53,10 +55,19 @@ public SeriesController(ILogger logger, ITaskScheduler taskSch _recommendationCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRecommendations); } + /// + /// Gets series with the applied Filter + /// + /// This is considered v1 and no longer used by Kavita, but will be supported for sometime. See series/v2 + /// + /// + /// + /// [HttpPost] + [Obsolete("use v2")] public async Task>> GetSeriesForLibrary(int libraryId, [FromQuery] UserParams userParams, [FromBody] FilterDto filterDto) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + var userId = User.GetUserId(); var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto); @@ -70,6 +81,30 @@ public async Task>> GetSeriesForLibrary(int lib return Ok(series); } + /// + /// Gets series with the applied Filter + /// + /// + /// + /// + [HttpPost("v2")] + public async Task>> GetSeriesForLibraryV2([FromQuery] UserParams userParams, [FromBody] FilterV2Dto filterDto) + { + var userId = User.GetUserId(); + var series = + await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, userParams, filterDto); + + //TODO: We might want something like libraryId as source so that I don't have to muck with the groups + + // Apply progress/rating information (I can't work out how to do this in initial query) + if (series == null) return BadRequest("Could not get series for library"); + + await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); + Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); + + return Ok(series); + } + /// /// Fetches a Series for a given Id /// @@ -79,8 +114,7 @@ public async Task>> GetSeriesForLibrary(int lib [HttpGet("{seriesId:int}")] public async Task> GetSeries(int seriesId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId); + var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, User.GetUserId()); if (series == null) return NoContent(); return Ok(series); } @@ -115,15 +149,13 @@ public async Task DeleteMultipleSeries(DeleteSeriesDto dto) [HttpGet("volumes")] public async Task>> GetVolumes(int seriesId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, userId)); + return Ok(await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, User.GetUserId())); } [HttpGet("volume")] public async Task> GetVolume(int volumeId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - var vol = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId); + var vol = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, User.GetUserId()); if (vol == null) return NoContent(); return Ok(vol); } @@ -207,7 +239,7 @@ public async Task UpdateSeries(UpdateSeriesDto updateSeries) } /// - /// Gets all recently added series + /// Gets all recently added series. Obsolete, use recently-added-v2 /// /// /// @@ -215,9 +247,10 @@ public async Task UpdateSeries(UpdateSeriesDto updateSeries) /// [ResponseCache(CacheProfileName = "Instant")] [HttpPost("recently-added")] + [Obsolete("use recently-added-v2")] public async Task>> GetRecentlyAdded(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + var userId = User.GetUserId(); var series = await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, userId, userParams, filterDto); @@ -231,6 +264,30 @@ public async Task>> GetRecentlyAdded(FilterD return Ok(series); } + /// + /// Gets all recently added series + /// + /// + /// + /// + [ResponseCache(CacheProfileName = "Instant")] + [HttpPost("recently-added-v2")] + public async Task>> GetRecentlyAddedV2(FilterV2Dto filterDto, [FromQuery] UserParams userParams) + { + var userId = User.GetUserId(); + var series = + await _unitOfWork.SeriesRepository.GetRecentlyAddedV2(userId, userParams, filterDto); + + // Apply progress/rating information (I can't work out how to do this in initial query) + if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series")); + + await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); + + Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); + + return Ok(series); + } + /// /// Returns series that were recently updated, like adding or removing a chapter /// @@ -239,8 +296,7 @@ public async Task>> GetRecentlyAdded(FilterD [HttpPost("recently-updated-series")] public async Task>> GetRecentlyAddedChapters() { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.SeriesRepository.GetRecentlyUpdatedSeries(userId, 20)); + return Ok(await _unitOfWork.SeriesRepository.GetRecentlyUpdatedSeries(User.GetUserId(), 20)); } /// @@ -250,10 +306,35 @@ public async Task>> GetRecentlyAd /// /// /// + [HttpPost("all-v2")] + public async Task>> GetAllSeriesV2(FilterV2Dto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) + { + var userId = User.GetUserId(); + var series = + await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, userParams, filterDto); + + // Apply progress/rating information (I can't work out how to do this in initial query) + if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series")); + + await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); + + Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); + + return Ok(series); + } + + /// + /// Returns all series for the library. Obsolete, use all-v2 + /// + /// + /// + /// + /// [HttpPost("all")] + [Obsolete("User all-v2")] public async Task>> GetAllSeries(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + var userId = User.GetUserId(); var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto); @@ -270,16 +351,15 @@ public async Task>> GetAllSeries(FilterDto f /// /// Fetches series that are on deck aka have progress on them. /// - /// /// /// Default of 0 meaning all libraries /// [ResponseCache(CacheProfileName = "Instant")] [HttpPost("on-deck")] - public async Task>> GetOnDeck(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) + public async Task>> GetOnDeck([FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - var pagedList = await _unitOfWork.SeriesRepository.GetOnDeck(userId, libraryId, userParams, filterDto); + var userId = User.GetUserId(); + var pagedList = await _unitOfWork.SeriesRepository.GetOnDeck(userId, libraryId, userParams, null); await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, pagedList); @@ -288,6 +368,7 @@ public async Task>> GetOnDeck(FilterDto filt return Ok(pagedList); } + /// /// Removes a series from displaying on deck until the next read event on that series /// @@ -359,25 +440,24 @@ public async Task> GetSeriesMetadata(int seriesI [HttpPost("metadata")] public async Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto) { - if (await _seriesService.UpdateSeriesMetadata(updateSeriesMetadataDto)) + if (!await _seriesService.UpdateSeriesMetadata(updateSeriesMetadataDto)) + return BadRequest(await _localizationService.Translate(User.GetUserId(), "update-metadata-fail")); + + if (await _licenseService.HasActiveLicense()) { - if (await _licenseService.HasActiveLicense()) + _logger.LogDebug("Clearing cache as series weblinks may have changed"); + await _reviewCacheProvider.RemoveAsync(ReviewController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId); + await _ratingCacheProvider.RemoveAsync(RatingController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId); + + var allUsers = (await _unitOfWork.UserRepository.GetAllUsersAsync()).Select(s => s.Id); + foreach (var userId in allUsers) { - _logger.LogDebug("Clearing cache as series weblinks may have changed"); - await _reviewCacheProvider.RemoveAsync(ReviewController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId); - await _ratingCacheProvider.RemoveAsync(RatingController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId); - - var allUsers = (await _unitOfWork.UserRepository.GetAllUsersAsync()).Select(s => s.Id); - foreach (var userId in allUsers) - { - await _recommendationCacheProvider.RemoveAsync(RecommendedController.CacheKey + $"{updateSeriesMetadataDto.SeriesMetadata.SeriesId}-{userId}"); - } + await _recommendationCacheProvider.RemoveAsync(RecommendedController.CacheKey + $"{updateSeriesMetadataDto.SeriesMetadata.SeriesId}-{userId}"); } - - return Ok(await _localizationService.Translate(User.GetUserId(), "series-updated")); } - return BadRequest(await _localizationService.Translate(User.GetUserId(), "update-metadata-fail")); + return Ok(await _localizationService.Translate(User.GetUserId(), "series-updated")); + } /// @@ -389,7 +469,7 @@ public async Task UpdateSeriesMetadata(UpdateSeriesMetadataDto upd [HttpGet("series-by-collection")] public async Task>> GetSeriesByCollectionTag(int collectionId, [FromQuery] UserParams userParams) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + var userId = User.GetUserId(); var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForCollectionAsync(collectionId, userId, userParams); @@ -412,8 +492,7 @@ public async Task>> GetSeriesByCollectionTag public async Task>> GetAllSeriesById(SeriesByIdsDto dto) { if (dto.SeriesIds == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-payload")); - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(dto.SeriesIds, userId)); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(dto.SeriesIds, User.GetUserId())); } /// @@ -443,10 +522,9 @@ public async Task> GetAgeRating(int ageRating) [HttpGet("series-detail")] public async Task> GetSeriesDetailBreakdown(int seriesId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); try { - return await _seriesService.GetSeriesDetail(seriesId, userId); + return await _seriesService.GetSeriesDetail(seriesId, User.GetUserId()); } catch (KavitaException ex) { @@ -465,9 +543,7 @@ public async Task> GetSeriesDetailBreakdown(int se [HttpGet("related")] public async Task>> GetRelatedSeries(int seriesId, RelationKind relation) { - // Send back a custom DTO with each type or maybe sorted in some way - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _unitOfWork.SeriesRepository.GetSeriesForRelationKind(userId, seriesId, relation)); + return Ok(await _unitOfWork.SeriesRepository.GetSeriesForRelationKind(User.GetUserId(), seriesId, relation)); } /// @@ -478,8 +554,7 @@ public async Task>> GetRelatedSeries(int ser [HttpGet("all-related")] public async Task> GetAllRelatedSeries(int seriesId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _seriesService.GetRelatedSeries(userId, seriesId)); + return Ok(await _seriesService.GetRelatedSeries(User.GetUserId(), seriesId)); } diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index f1849a7ff2..895a0b5c3d 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -197,6 +197,12 @@ public async Task> UpdateSettings(ServerSettingDt _unitOfWork.SettingsRepository.Update(setting); } + if (setting.Key == ServerSettingKey.CoverImageSize && updateSettingsDto.CoverImageSize + string.Empty != setting.Value) + { + setting.Value = updateSettingsDto.CoverImageSize + string.Empty; + _unitOfWork.SettingsRepository.Update(setting); + } + if (setting.Key == ServerSettingKey.TaskScan && updateSettingsDto.TaskScan != setting.Value) { setting.Value = updateSettingsDto.TaskScan; diff --git a/API/Controllers/StatsController.cs b/API/Controllers/StatsController.cs index d711d5f475..625ff38ba0 100644 --- a/API/Controllers/StatsController.cs +++ b/API/Controllers/StatsController.cs @@ -125,14 +125,21 @@ public async Task>>> Rea } [HttpGet("day-breakdown")] - [Authorize("RequireAdminRole")] [ResponseCache(CacheProfileName = "Statistics")] - public ActionResult>> GetDayBreakdown() + public async Task>>> GetDayBreakdown(int userId = 0) { - return Ok(_statService.GetDayBreakdown()); + if (userId == 0) + { + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); + var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user); + if (!isAdmin) return BadRequest(); + } + + return Ok(_statService.GetDayBreakdown(userId)); } + [HttpGet("user/reading-history")] [ResponseCache(CacheProfileName = "Statistics")] public async Task>> GetReadingHistory(int userId) diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 755646c334..9358bd4066 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -68,10 +68,9 @@ public async Task>> GetMyself() [HttpGet("has-reading-progress")] public async Task> HasReadingProgress(int libraryId) { - var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId); if (library == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-doesnt-exist")); - return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, userId)); + return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, User.GetUserId())); } [HttpGet("has-library-access")] diff --git a/API/Controllers/WantToReadController.cs b/API/Controllers/WantToReadController.cs index 563a559956..3fb33a822e 100644 --- a/API/Controllers/WantToReadController.cs +++ b/API/Controllers/WantToReadController.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using API.Data; using API.Data.Repositories; using API.DTOs; using API.DTOs.Filtering; +using API.DTOs.Filtering.v2; using API.DTOs.WantToRead; using API.Extensions; using API.Helpers; @@ -33,12 +35,13 @@ public WantToReadController(IUnitOfWork unitOfWork, IScrobblingService scrobblin } /// - /// Return all Series that are in the current logged in user's Want to Read list, filtered + /// Return all Series that are in the current logged in user's Want to Read list, filtered (deprecated, use v2) /// /// /// /// [HttpPost] + [Obsolete("use v2 instead")] public async Task>> GetWantToRead([FromQuery] UserParams userParams, FilterDto filterDto) { userParams ??= new UserParams(); @@ -50,6 +53,24 @@ public async Task>> GetWantToRead([FromQuery] return Ok(pagedList); } + /// + /// Return all Series that are in the current logged in user's Want to Read list, filtered + /// + /// + /// + /// + [HttpPost("v2")] + public async Task>> GetWantToReadV2([FromQuery] UserParams userParams, FilterV2Dto filterDto) + { + userParams ??= new UserParams(); + var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserV2Async(User.GetUserId(), userParams, filterDto); + Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages); + + await _unitOfWork.SeriesRepository.AddSeriesModifiers(User.GetUserId(), pagedList); + + return Ok(pagedList); + } + [HttpGet] public async Task> IsSeriesInWantToRead([FromQuery] int seriesId) { diff --git a/API/DTOs/Filtering/v2/FilterCombination.cs b/API/DTOs/Filtering/v2/FilterCombination.cs new file mode 100644 index 0000000000..d011cb000f --- /dev/null +++ b/API/DTOs/Filtering/v2/FilterCombination.cs @@ -0,0 +1,7 @@ +namespace API.DTOs.Filtering.v2; + +public enum FilterCombination +{ + Or = 0, + And = 1 +} diff --git a/API/DTOs/Filtering/v2/FilterComparision.cs b/API/DTOs/Filtering/v2/FilterComparision.cs new file mode 100644 index 0000000000..109667dad9 --- /dev/null +++ b/API/DTOs/Filtering/v2/FilterComparision.cs @@ -0,0 +1,56 @@ +using System.ComponentModel; + +namespace API.DTOs.Filtering.v2; + +public enum FilterComparison +{ + [Description("Equal")] + Equal = 0, + GreaterThan = 1, + GreaterThanEqual = 2, + LessThan = 3, + LessThanEqual = 4, + /// + /// value is within any of the series. This is inheritently an OR, even if combinator is an AND + /// + /// Only works with IList + Contains = 5, + /// + /// value is within All of the series. This is an AND, even if combinator ORs the different statements + /// + /// Only works with IList + MustContains = 6, + /// + /// Performs a LIKE %value% + /// + Matches = 7, + NotContains = 8, + /// + /// Not Equal to + /// + NotEqual = 9, + /// + /// String starts with + /// + BeginsWith = 10, + /// + /// String ends with + /// + EndsWith = 11, + /// + /// Is Date before X + /// + IsBefore = 12, + /// + /// Is Date after X + /// + IsAfter = 13, + /// + /// Is Date between now and X seconds ago + /// + IsInLast = 14, + /// + /// Is Date not between now and X seconds ago + /// + IsNotInLast = 15, +} diff --git a/API/DTOs/Filtering/v2/FilterField.cs b/API/DTOs/Filtering/v2/FilterField.cs new file mode 100644 index 0000000000..73fef1c370 --- /dev/null +++ b/API/DTOs/Filtering/v2/FilterField.cs @@ -0,0 +1,40 @@ +namespace API.DTOs.Filtering.v2; + +/// +/// Represents the field which will dictate the value type and the Extension used for filtering +/// +public enum FilterField +{ + Summary = 0, + SeriesName = 1, + PublicationStatus = 2, + Languages = 3, + AgeRating = 4, + UserRating = 5, + Tags = 6, + CollectionTags = 7, + Translators = 8, + Characters = 9, + Publisher = 10, + Editor = 11, + CoverArtist = 12, + Letterer = 13, + Colorist = 14, + Inker = 15, + Penciller = 16, + Writers = 17, + Genres = 18, + Libraries = 19, + ReadProgress = 20, + Formats = 21, + ReleaseYear = 22, + ReadTime = 23, + /// + /// Series Folder + /// + Path = 24, + /// + /// File path + /// + FilePath = 25 +} diff --git a/API/DTOs/Filtering/v2/FilterStatementDto.cs b/API/DTOs/Filtering/v2/FilterStatementDto.cs new file mode 100644 index 0000000000..a6192093ea --- /dev/null +++ b/API/DTOs/Filtering/v2/FilterStatementDto.cs @@ -0,0 +1,8 @@ +namespace API.DTOs.Filtering.v2; + +public class FilterStatementDto +{ + public FilterComparison Comparison { get; set; } + public FilterField Field { get; set; } + public string Value { get; set; } +} diff --git a/API/DTOs/Filtering/v2/FilterV2Dto.cs b/API/DTOs/Filtering/v2/FilterV2Dto.cs new file mode 100644 index 0000000000..2dff500f7d --- /dev/null +++ b/API/DTOs/Filtering/v2/FilterV2Dto.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + + +namespace API.DTOs.Filtering.v2; + + + +/// +/// Metadata filtering for v2 API only +/// +public class FilterV2Dto +{ + /// + /// The name of the filter + /// + public string? Name { get; set; } + public ICollection Statements { get; set; } = new List(); + public FilterCombination Combination { get; set; } = FilterCombination.And; + public SortOptions SortOptions { get; set; } + + /// + /// Limit the number of rows returned. Defaults to not applying a limit (aka 0) + /// + public int LimitTo { get; set; } = 0; +} + + + + + diff --git a/API/DTOs/Reader/BookmarkDto.cs b/API/DTOs/Reader/BookmarkDto.cs index b132eb9585..a6b1856836 100644 --- a/API/DTOs/Reader/BookmarkDto.cs +++ b/API/DTOs/Reader/BookmarkDto.cs @@ -13,4 +13,8 @@ public class BookmarkDto public int SeriesId { get; set; } [Required] public int ChapterId { get; set; } + /// + /// This is only used when getting all bookmarks. + /// + public SeriesDto? Series { get; set; } } diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs index 59b7708cbf..a8ec37d9c9 100644 --- a/API/DTOs/SeriesDto.cs +++ b/API/DTOs/SeriesDto.cs @@ -12,7 +12,6 @@ public class SeriesDto : IHasReadTimeEstimate public string? OriginalName { get; init; } public string? LocalizedName { get; init; } public string? SortName { get; init; } - public string? Summary { get; init; } public int Pages { get; init; } public bool CoverImageLocked { get; set; } /// diff --git a/API/DTOs/Settings/ServerSettingDTO.cs b/API/DTOs/Settings/ServerSettingDTO.cs index 15dd9177b8..e405758bc9 100644 --- a/API/DTOs/Settings/ServerSettingDTO.cs +++ b/API/DTOs/Settings/ServerSettingDTO.cs @@ -84,4 +84,8 @@ public class ServerSettingDto /// How many Days since today in the past for chapter updates, should content be considered for On Deck, before it gets removed automatically /// public int OnDeckUpdateDays { get; set; } + /// + /// How large the cover images should be + /// + public CoverImageSize CoverImageSize { get; set; } } diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index 48c49423c5..adc04905d5 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class DataContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.9"); + modelBuilder.HasAnnotation("ProductVersion", "7.0.10"); modelBuilder.Entity("API.Entities.AppRole", b => { @@ -180,7 +180,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("AppUserId"); - b.ToTable("AppUserBookmark"); + b.ToTable("AppUserBookmark", (string)null); }); modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => @@ -201,7 +201,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("AppUserOnDeckRemoval"); + b.ToTable("AppUserOnDeckRemoval", (string)null); }); modelBuilder.Entity("API.Entities.AppUserPreferences", b => @@ -315,7 +315,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ThemeId"); - b.ToTable("AppUserPreferences"); + b.ToTable("AppUserPreferences", (string)null); }); modelBuilder.Entity("API.Entities.AppUserProgress", b => @@ -365,7 +365,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("AppUserProgresses"); + b.ToTable("AppUserProgresses", (string)null); }); modelBuilder.Entity("API.Entities.AppUserRating", b => @@ -398,7 +398,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("AppUserRating"); + b.ToTable("AppUserRating", (string)null); }); modelBuilder.Entity("API.Entities.AppUserRole", b => @@ -466,7 +466,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("AppUserTableOfContent"); + b.ToTable("AppUserTableOfContent", (string)null); }); modelBuilder.Entity("API.Entities.Chapter", b => @@ -576,7 +576,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("VolumeId"); - b.ToTable("Chapter"); + b.ToTable("Chapter", (string)null); }); modelBuilder.Entity("API.Entities.CollectionTag", b => @@ -611,7 +611,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Id", "Promoted") .IsUnique(); - b.ToTable("CollectionTag"); + b.ToTable("CollectionTag", (string)null); }); modelBuilder.Entity("API.Entities.Device", b => @@ -657,7 +657,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("AppUserId"); - b.ToTable("Device"); + b.ToTable("Device", (string)null); }); modelBuilder.Entity("API.Entities.FolderPath", b => @@ -679,7 +679,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("LibraryId"); - b.ToTable("FolderPath"); + b.ToTable("FolderPath", (string)null); }); modelBuilder.Entity("API.Entities.Genre", b => @@ -699,7 +699,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedTitle") .IsUnique(); - b.ToTable("Genre"); + b.ToTable("Genre", (string)null); }); modelBuilder.Entity("API.Entities.Library", b => @@ -757,7 +757,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Library"); + b.ToTable("Library", (string)null); }); modelBuilder.Entity("API.Entities.MangaFile", b => @@ -806,7 +806,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ChapterId"); - b.ToTable("MangaFile"); + b.ToTable("MangaFile", (string)null); }); modelBuilder.Entity("API.Entities.MediaError", b => @@ -841,7 +841,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("MediaError"); + b.ToTable("MediaError", (string)null); }); modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => @@ -942,7 +942,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Id", "SeriesId") .IsUnique(); - b.ToTable("SeriesMetadata"); + b.ToTable("SeriesMetadata", (string)null); }); modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => @@ -966,7 +966,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("TargetSeriesId"); - b.ToTable("SeriesRelation"); + b.ToTable("SeriesRelation", (string)null); }); modelBuilder.Entity("API.Entities.Person", b => @@ -986,7 +986,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Person"); + b.ToTable("Person", (string)null); }); modelBuilder.Entity("API.Entities.ReadingList", b => @@ -1049,7 +1049,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("AppUserId"); - b.ToTable("ReadingList"); + b.ToTable("ReadingList", (string)null); }); modelBuilder.Entity("API.Entities.ReadingListItem", b => @@ -1083,7 +1083,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("VolumeId"); - b.ToTable("ReadingListItem"); + b.ToTable("ReadingListItem", (string)null); }); modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => @@ -1128,7 +1128,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("ScrobbleError"); + b.ToTable("ScrobbleError", (string)null); }); modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => @@ -1188,8 +1188,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SeriesId") .HasColumnType("INTEGER"); - b.Property("VolumeNumber") - .HasColumnType("INTEGER"); + b.Property("VolumeNumber") + .HasColumnType("REAL"); b.HasKey("Id"); @@ -1199,7 +1199,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("ScrobbleEvent"); + b.ToTable("ScrobbleEvent", (string)null); }); modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => @@ -1232,7 +1232,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("ScrobbleHold"); + b.ToTable("ScrobbleHold", (string)null); }); modelBuilder.Entity("API.Entities.Series", b => @@ -1328,7 +1328,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("LibraryId"); - b.ToTable("Series"); + b.ToTable("Series", (string)null); }); modelBuilder.Entity("API.Entities.ServerSetting", b => @@ -1345,7 +1345,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Key"); - b.ToTable("ServerSetting"); + b.ToTable("ServerSetting", (string)null); }); modelBuilder.Entity("API.Entities.ServerStatistics", b => @@ -1383,7 +1383,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("ServerStatistics"); + b.ToTable("ServerStatistics", (string)null); }); modelBuilder.Entity("API.Entities.SiteTheme", b => @@ -1421,7 +1421,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("SiteTheme"); + b.ToTable("SiteTheme", (string)null); }); modelBuilder.Entity("API.Entities.Tag", b => @@ -1441,7 +1441,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedTitle") .IsUnique(); - b.ToTable("Tag"); + b.ToTable("Tag", (string)null); }); modelBuilder.Entity("API.Entities.Volume", b => @@ -1477,8 +1477,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .HasColumnType("TEXT"); - b.Property("Number") - .HasColumnType("INTEGER"); + b.Property("Number") + .HasColumnType("REAL"); b.Property("Pages") .HasColumnType("INTEGER"); @@ -1493,7 +1493,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesId"); - b.ToTable("Volume"); + b.ToTable("Volume", (string)null); }); modelBuilder.Entity("AppUserLibrary", b => @@ -1508,7 +1508,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("LibrariesId"); - b.ToTable("AppUserLibrary"); + b.ToTable("AppUserLibrary", (string)null); }); modelBuilder.Entity("ChapterGenre", b => @@ -1523,7 +1523,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("GenresId"); - b.ToTable("ChapterGenre"); + b.ToTable("ChapterGenre", (string)null); }); modelBuilder.Entity("ChapterPerson", b => @@ -1538,7 +1538,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PeopleId"); - b.ToTable("ChapterPerson"); + b.ToTable("ChapterPerson", (string)null); }); modelBuilder.Entity("ChapterTag", b => @@ -1553,7 +1553,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("TagsId"); - b.ToTable("ChapterTag"); + b.ToTable("ChapterTag", (string)null); }); modelBuilder.Entity("CollectionTagSeriesMetadata", b => @@ -1568,7 +1568,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesMetadatasId"); - b.ToTable("CollectionTagSeriesMetadata"); + b.ToTable("CollectionTagSeriesMetadata", (string)null); }); modelBuilder.Entity("GenreSeriesMetadata", b => @@ -1583,7 +1583,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesMetadatasId"); - b.ToTable("GenreSeriesMetadata"); + b.ToTable("GenreSeriesMetadata", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -1682,7 +1682,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SeriesMetadatasId"); - b.ToTable("PersonSeriesMetadata"); + b.ToTable("PersonSeriesMetadata", (string)null); }); modelBuilder.Entity("SeriesMetadataTag", b => @@ -1697,7 +1697,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("TagsId"); - b.ToTable("SeriesMetadataTag"); + b.ToTable("SeriesMetadataTag", (string)null); }); modelBuilder.Entity("API.Entities.AppUserBookmark", b => diff --git a/API/Data/Repositories/CollectionTagRepository.cs b/API/Data/Repositories/CollectionTagRepository.cs index 5bc490e352..4e35ef6138 100644 --- a/API/Data/Repositories/CollectionTagRepository.cs +++ b/API/Data/Repositories/CollectionTagRepository.cs @@ -127,7 +127,6 @@ public async Task> GetRandomCoverImagesAsync(int collectionId) .Select(sm => sm.Series.CoverImage) .Where(t => !string.IsNullOrEmpty(t)) .ToListAsync(); - if (data.Count < 4) return new List(); return data .OrderBy(_ => random.Next()) .Take(4) diff --git a/API/Data/Repositories/LibraryRepository.cs b/API/Data/Repositories/LibraryRepository.cs index 065ce4a0f3..8a98b1dbe8 100644 --- a/API/Data/Repositories/LibraryRepository.cs +++ b/API/Data/Repositories/LibraryRepository.cs @@ -11,6 +11,7 @@ using API.Entities.Enums; using API.Extensions; using API.Extensions.QueryExtensions; +using API.Services.Tasks.Scanner.Parser; using AutoMapper; using AutoMapper.QueryableExtensions; using Kavita.Common.Extensions; @@ -45,14 +46,14 @@ public interface ILibraryRepository Task GetTotalFiles(); IEnumerable GetJumpBarAsync(int libraryId); Task> GetAllAgeRatingsDtosForLibrariesAsync(List libraryIds); - Task> GetAllLanguagesForLibrariesAsync(List libraryIds); - Task> GetAllLanguagesForLibrariesAsync(); + Task> GetAllLanguagesForLibrariesAsync(List? libraryIds); IEnumerable GetAllPublicationStatusesDtosForLibrariesAsync(List libraryIds); Task DoAnySeriesFoldersMatch(IEnumerable folders); Task GetLibraryCoverImageAsync(int libraryId); Task> GetAllCoverImagesAsync(); Task> GetLibraryTypesForIdsAsync(IEnumerable libraryIds); Task> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat); + Task GetAllowsScrobblingBySeriesId(int seriesId); } public class LibraryRepository : ILibraryRepository @@ -260,10 +261,10 @@ public async Task> GetAllAgeRatingsDtosForLibrariesAsync(Lis .ToListAsync(); } - public async Task> GetAllLanguagesForLibrariesAsync(List libraryIds) + public async Task> GetAllLanguagesForLibrariesAsync(List? libraryIds) { var ret = await _context.Series - .Where(s => libraryIds.Contains(s.LibraryId)) + .WhereIf(libraryIds is {Count: > 0} , s => libraryIds.Contains(s.LibraryId)) .Select(s => s.Metadata.Language) .AsSplitQuery() .AsNoTracking() @@ -272,33 +273,33 @@ public async Task> GetAllLanguagesForLibrariesAsync(List return ret .Where(s => !string.IsNullOrEmpty(s)) - .Select(s => new LanguageDto() - { - Title = CultureInfo.GetCultureInfo(s).DisplayName, - IsoCode = s - }) + .DistinctBy(Parser.Normalize) + .Select(GetCulture) + .Where(s => s != null) .OrderBy(s => s.Title) .ToList(); } - public async Task> GetAllLanguagesForLibrariesAsync() + private static LanguageDto GetCulture(string s) { - var ret = await _context.Series - .Select(s => s.Metadata.Language) - .AsSplitQuery() - .AsNoTracking() - .Distinct() - .ToListAsync(); - - return ret - .Where(s => !string.IsNullOrEmpty(s)) - .Select(s => new LanguageDto() + try + { + return new LanguageDto() { Title = CultureInfo.GetCultureInfo(s).DisplayName, IsoCode = s - }) - .OrderBy(s => s.Title) - .ToList(); + }; + } + catch (Exception) + { + // ignored + } + + return new LanguageDto() + { + Title = s, + IsoCode = s + };; } public IEnumerable GetAllPublicationStatusesDtosForLibrariesAsync(List libraryIds) @@ -374,4 +375,11 @@ public async Task> GetAllWithCoversInDifferentEncoding(EncodeForm .Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension)) .ToListAsync(); } + + public async Task GetAllowsScrobblingBySeriesId(int seriesId) + { + return await _context.Series.Where(s => s.Id == seriesId) + .Select(s => s.Library.AllowScrobbling) + .SingleOrDefaultAsync(); + } } diff --git a/API/Data/Repositories/PersonRepository.cs b/API/Data/Repositories/PersonRepository.cs index 0e05a66722..a6329f8877 100644 --- a/API/Data/Repositories/PersonRepository.cs +++ b/API/Data/Repositories/PersonRepository.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using API.DTOs; using API.Entities; +using API.Entities.Enums; using API.Extensions; using API.Extensions.QueryExtensions; using AutoMapper; @@ -17,9 +18,11 @@ public interface IPersonRepository void Remove(Person person); Task> GetAllPeople(); Task> GetAllPersonDtosAsync(int userId); + Task> GetAllPersonDtosByRoleAsync(int userId, PersonRole role); Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false); Task> GetAllPeopleDtosForLibrariesAsync(List libraryIds, int userId); Task GetCountAsync(); + } public class PersonRepository : IPersonRepository @@ -94,4 +97,15 @@ public async Task> GetAllPersonDtosAsync(int userId) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } + + public async Task> GetAllPersonDtosByRoleAsync(int userId, PersonRole role) + { + var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); + return await _context.Person + .Where(p => p.Role == role) + .OrderBy(p => p.Name) + .RestrictAgainstAgeRestriction(ageRating) + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); + } } diff --git a/API/Data/Repositories/ReadingListRepository.cs b/API/Data/Repositories/ReadingListRepository.cs index bd0a5bafc5..9c3d400118 100644 --- a/API/Data/Repositories/ReadingListRepository.cs +++ b/API/Data/Repositories/ReadingListRepository.cs @@ -101,7 +101,6 @@ public async Task> GetRandomCoverImagesAsync(int readingListId) .SelectMany(r => r.Items.Select(ri => ri.Chapter.CoverImage)) .Where(t => !string.IsNullOrEmpty(t)) .ToListAsync(); - if (data.Count < 4) return new List(); return data .OrderBy(_ => random.Next()) .Take(4) diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 9a5534b948..3457469b4b 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -10,6 +9,7 @@ using API.DTOs; using API.DTOs.CollectionTags; using API.DTOs.Filtering; +using API.DTOs.Filtering.v2; using API.DTOs.Metadata; using API.DTOs.ReadingLists; using API.DTOs.Search; @@ -20,7 +20,9 @@ using API.Entities.Metadata; using API.Extensions; using API.Extensions.QueryExtensions; +using API.Extensions.QueryExtensions.Filtering; using API.Helpers; +using API.Helpers.Converters; using API.Services; using API.Services.Tasks; using API.Services.Tasks.Scanner; @@ -95,8 +97,9 @@ public interface ISeriesRepository /// Task AddSeriesModifiers(int userId, IList series); Task GetSeriesCoverImageAsync(int seriesId); - Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto filter); + Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto? filter); Task> GetRecentlyAdded(int libraryId, int userId, UserParams userParams, FilterDto filter); + Task> GetRecentlyAddedV2(int userId, UserParams userParams, FilterV2Dto filter); Task GetSeriesMetadata(int seriesId); Task> GetSeriesDtoForCollectionAsync(int collectionId, int userId, UserParams userParams); Task> GetFilesForSeries(int seriesId); @@ -118,6 +121,7 @@ public interface ISeriesRepository Task GetSeriesForMangaFile(int mangaFileId, int userId); Task GetSeriesForChapter(int chapterId, int userId); Task> GetWantToReadForUserAsync(int userId, UserParams userParams, FilterDto filter); + Task> GetWantToReadForUserV2Async(int userId, UserParams userParams, FilterV2Dto filter); Task> GetWantToReadForUserAsync(int userId); Task IsSeriesInWantToRead(int userId, int seriesId); Task GetSeriesByFolderPath(string folder, SeriesIncludes includes = SeriesIncludes.None); @@ -140,6 +144,7 @@ Task> GetAllSeriesDtosByNameAsync(IEnumerable nor Task GetAverageUserRating(int seriesId, int userId); Task RemoveFromOnDeck(int seriesId, int userId); Task ClearOnDeckRemoval(int seriesId, int userId); + Task> GetSeriesDtoForLibraryIdV2Async(int userId, UserParams userParams, FilterV2Dto filterDto); } public class SeriesRepository : ISeriesRepository @@ -300,6 +305,7 @@ public async Task> GetFullSeriesForLibraryIdAsync(int libraryI /// /// /// + [Obsolete("Use GetSeriesDtoForLibraryIdAsync")] public async Task> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId, UserParams userParams, FilterDto filter) { var query = await CreateFilteredSearchQueryable(userId, libraryId, filter, QueryContext.None); @@ -605,6 +611,18 @@ public async Task> GetAllWithCoversInDifferentEncoding(EncodeForma return await query.ToListAsync(); } + public async Task> GetSeriesDtoForLibraryIdV2Async(int userId, UserParams userParams, FilterV2Dto filterDto) + { + var query = await CreateFilteredSearchQueryableV2(userId, filterDto, QueryContext.None); + + var retSeries = query + .ProjectTo(_mapper.ConfigurationProvider) + .AsSplitQuery() + .AsNoTracking(); + + return await PagedList.CreateAsync(retSeries, userParams.PageNumber, userParams.PageSize); + } + public async Task AddSeriesModifiers(int userId, IList series) { @@ -644,7 +662,6 @@ public async Task AddSeriesModifiers(int userId, IList series) } - /// /// Returns a list of Series that were added, ordered by Created desc /// @@ -653,6 +670,7 @@ public async Task AddSeriesModifiers(int userId, IList series) /// Contains pagination information /// Optional filter on query /// + [Obsolete("Use GetRecentlyAddedV2")] public async Task> GetRecentlyAdded(int libraryId, int userId, UserParams userParams, FilterDto filter) { var query = await CreateFilteredSearchQueryable(userId, libraryId, filter, QueryContext.Dashboard); @@ -666,6 +684,19 @@ public async Task> GetRecentlyAdded(int libraryId, int user return await PagedList.CreateAsync(retSeries, userParams.PageNumber, userParams.PageSize); } + public async Task> GetRecentlyAddedV2(int userId, UserParams userParams, FilterV2Dto filter) + { + var query = await CreateFilteredSearchQueryableV2(userId, filter, QueryContext.Dashboard); + + var retSeries = query + .OrderByDescending(s => s.Created) + .ProjectTo(_mapper.ConfigurationProvider) + .AsSplitQuery() + .AsNoTracking(); + + return await PagedList.CreateAsync(retSeries, userParams.PageNumber, userParams.PageSize); + } + private IList ExtractFilters(int libraryId, int userId, FilterDto filter, ref List userLibraries, out List allPeopleIds, out bool hasPeopleFilter, out bool hasGenresFilter, out bool hasCollectionTagFilter, out bool hasRatingFilter, out bool hasProgressFilter, out IList seriesIds, out bool hasAgeRating, out bool hasTagsFilter, @@ -759,7 +790,7 @@ bool ProgressComparison(int pagesRead, int totalPages) /// Pagination information /// Optional (default null) filter on query /// - public async Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto filter) + public async Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto? filter) { var settings = await _context.ServerSetting .Select(x => x) @@ -780,11 +811,6 @@ public async Task> GetOnDeck(int userId, int libraryId, Use .Select(d => d.SeriesId) .AsEnumerable(); - // var onDeckRemovals = _context.AppUser.Where(u => u.Id == userId) - // .SelectMany(u => u.OnDeckRemovals.Select(d => d.Id)) - // .AsEnumerable(); - - var query = _context.Series .Where(s => usersSeriesIds.Contains(s.Id)) .Where(s => !onDeckRemovals.Contains(s.Id)) @@ -828,29 +854,47 @@ private async Task> CreateFilteredSearchQueryable(int userId, var query = _context.Series .AsNoTracking() - .WhereIf(hasGenresFilter, s => s.Metadata.Genres.Any(g => filter.Genres.Contains(g.Id))) - .WhereIf(hasPeopleFilter, s => s.Metadata.People.Any(p => allPeopleIds.Contains(p.Id))) - .WhereIf(hasCollectionTagFilter, - s => s.Metadata.CollectionTags.Any(t => filter.CollectionTags.Contains(t.Id))) - .WhereIf(hasRatingFilter, s => s.Ratings.Any(r => r.Rating >= filter.Rating && r.AppUserId == userId)) - .WhereIf(hasProgressFilter, s => seriesIds.Contains(s.Id)) - .WhereIf(hasAgeRating, s => filter.AgeRating.Contains(s.Metadata.AgeRating)) - .WhereIf(hasTagsFilter, s => s.Metadata.Tags.Any(t => filter.Tags.Contains(t.Id))) - .WhereIf(hasLanguageFilter, s => filter.Languages.Contains(s.Metadata.Language)) - .WhereIf(hasReleaseYearMinFilter, s => s.Metadata.ReleaseYear >= filter.ReleaseYearRange!.Min) - .WhereIf(hasReleaseYearMaxFilter, s => s.Metadata.ReleaseYear <= filter.ReleaseYearRange!.Max) - .WhereIf(hasPublicationFilter, s => filter.PublicationStatus.Contains(s.Metadata.PublicationStatus)) - .WhereIf(hasSeriesNameFilter, s => EF.Functions.Like(s.Name, $"%{filter.SeriesNameQuery}%") - || EF.Functions.Like(s.OriginalName!, $"%{filter.SeriesNameQuery}%") - || EF.Functions.Like(s.LocalizedName!, $"%{filter.SeriesNameQuery}%")) + // This new style can handle any filterComparision coming from the user + .HasLanguage(hasLanguageFilter, FilterComparison.Contains, filter.Languages) + .HasReleaseYear(hasReleaseYearMaxFilter, FilterComparison.LessThanEqual, filter.ReleaseYearRange?.Max) + .HasReleaseYear(hasReleaseYearMinFilter, FilterComparison.GreaterThanEqual, filter.ReleaseYearRange?.Min) + .HasName(hasSeriesNameFilter, FilterComparison.Matches, filter.SeriesNameQuery) + .HasRating(hasRatingFilter, FilterComparison.GreaterThanEqual, filter.Rating, userId) + .HasAgeRating(hasAgeRating, FilterComparison.Contains, filter.AgeRating) + .HasPublicationStatus(hasPublicationFilter, FilterComparison.Contains, filter.PublicationStatus) + .HasTags(hasTagsFilter, FilterComparison.Contains, filter.Tags) + .HasCollectionTags(hasCollectionTagFilter, FilterComparison.Contains, filter.Tags) + .HasGenre(hasGenresFilter, FilterComparison.Contains, filter.Genres) + .HasFormat(filter.Formats != null && filter.Formats.Count > 0, FilterComparison.Contains, filter.Formats!) + .HasAverageReadTime(true, FilterComparison.GreaterThanEqual, 0) + + // TODO: This needs different treatment + .HasPeople(hasPeopleFilter, FilterComparison.Contains, allPeopleIds) .WhereIf(onlyParentSeries, s => s.RelationOf.Count == 0 || s.RelationOf.All(p => p.RelationKind == RelationKind.Prequel)) - .Where(s => userLibraries.Contains(s.LibraryId)) - .Where(s => formats.Contains(s.Format)); + .Where(s => userLibraries.Contains(s.LibraryId)); + + if (filter.ReadStatus.InProgress) + { + query = query.HasReadingProgress(hasProgressFilter, FilterComparison.GreaterThan, + 0, userId) + .HasReadingProgress(hasProgressFilter, FilterComparison.LessThan, + 100, userId); + } else if (filter.ReadStatus.Read) + { + query = query.HasReadingProgress(hasProgressFilter, FilterComparison.Equal, + 100, userId); + } + else if (filter.ReadStatus.NotRead) + { + query = query.HasReadingProgress(hasProgressFilter, FilterComparison.Equal, + 0, userId); + } if (userRating.AgeRating != AgeRating.NotApplicable) { + // this if statement is included in the extension query = query.RestrictAgainstAgeRestriction(userRating); } @@ -889,9 +933,141 @@ private async Task> CreateFilteredSearchQueryable(int userId, }; } + return query.AsSplitQuery(); + } + + private async Task> CreateFilteredSearchQueryableV2(int userId, FilterV2Dto filter, QueryContext queryContext, IQueryable? query = null) + { + var userLibraries = await GetUserLibrariesForFilteredQuery(0, userId, queryContext); + var userRating = await _context.AppUser.GetUserAgeRestriction(userId); + var onlyParentSeries = await _context.AppUserPreferences.Where(u => u.AppUserId == userId) + .Select(u => u.CollapseSeriesRelationships) + .SingleOrDefaultAsync(); + + query ??= _context.Series + .AsNoTracking(); + + + + // First setup any FilterField.Libraries in the statements, as these don't have any traditional query statements applied here + query = ApplyLibraryFilter(filter, query); + + query = BuildFilterQuery(userId, filter, query); + + + query = query + .WhereIf(userLibraries.Count > 0, s => userLibraries.Contains(s.LibraryId)) + .WhereIf(onlyParentSeries, s => + s.RelationOf.Count == 0 || + s.RelationOf.All(p => p.RelationKind == RelationKind.Prequel)) + .RestrictAgainstAgeRestriction(userRating); + + + return ApplyLimit(query + .Sort(filter.SortOptions) + .AsSplitQuery(), filter.LimitTo); + } + + private static IQueryable ApplyLibraryFilter(FilterV2Dto filter, IQueryable query) + { + var filterIncludeLibs = new List(); + var filterExcludeLibs = new List(); + if (filter.Statements != null) + { + foreach (var stmt in filter.Statements.Where(stmt => stmt.Field == FilterField.Libraries)) + { + var libIds = stmt.Value.Split(',').Select(int.Parse); + if (stmt.Comparison is FilterComparison.Equal or FilterComparison.Contains) + { + + filterIncludeLibs.AddRange(libIds); + } + else + { + filterExcludeLibs.AddRange(libIds); + } + } + + // Remove as filterLibs now has everything + filter.Statements = filter.Statements.Where(stmt => stmt.Field != FilterField.Libraries).ToList(); + } + + // We now have a list of libraries the user wants it restricted to and libraries the user doesn't want in the list + // We need to check what the filer combo is to see how to next approach + + if (filter.Combination == FilterCombination.And) + { + // If the filter combo is AND, then we need 2 different queries + query = query + .WhereIf(filterIncludeLibs.Count > 0, s => filterIncludeLibs.Contains(s.LibraryId)) + .WhereIf(filterExcludeLibs.Count > 0, s => !filterExcludeLibs.Contains(s.LibraryId)); + } + else + { + // This is an OR statement. In that case we can just remove the filterExcludes + query = query.WhereIf(filterIncludeLibs.Count > 0, s => filterIncludeLibs.Contains(s.LibraryId)); + } + return query; } + private static IQueryable BuildFilterQuery(int userId, FilterV2Dto filterDto, IQueryable query) + { + if (filterDto.Statements == null || !filterDto.Statements.Any()) return query; + + + var queries = filterDto.Statements + .Select(statement => BuildFilterGroup(userId, statement, query)) + .ToList(); + + return filterDto.Combination == FilterCombination.And + ? queries.Aggregate((q1, q2) => q1.Intersect(q2)) + : queries.Aggregate((q1, q2) => q1.Union(q2)); + } + + private static IQueryable ApplyLimit(IQueryable query, int limit) + { + return limit <= 0 ? query : query.Take(limit); + } + + private static IQueryable BuildFilterGroup(int userId, FilterStatementDto statement, IQueryable query) + { + var (value, _) = FilterFieldValueConverter.ConvertValue(statement.Field, statement.Value); + return statement.Field switch + { + FilterField.Summary => query.HasSummary(true, statement.Comparison, (string) value), + FilterField.SeriesName => query.HasName(true, statement.Comparison, (string) value), + FilterField.Path => query.HasPath(true, statement.Comparison, (string) value), + FilterField.FilePath => query.HasFilePath(true, statement.Comparison, (string) value), + FilterField.PublicationStatus => query.HasPublicationStatus(true, statement.Comparison, + (IList) value), + FilterField.Languages => query.HasLanguage(true, statement.Comparison, (IList) value), + FilterField.AgeRating => query.HasAgeRating(true, statement.Comparison, (IList) value), + FilterField.UserRating => query.HasRating(true, statement.Comparison, (int) value, userId), + FilterField.Tags => query.HasTags(true, statement.Comparison, (IList) value), + FilterField.CollectionTags => query.HasCollectionTags(true, statement.Comparison, (IList) value), + FilterField.Translators => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Characters => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Publisher => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Editor => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.CoverArtist => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Letterer => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Colorist => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Inker => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Penciller => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Writers => query.HasPeople(true, statement.Comparison, (IList) value), + FilterField.Genres => query.HasGenre(true, statement.Comparison, (IList) value), + FilterField.Libraries => + // This is handled in the code before this as it's handled in a more general, combined manner + query, + FilterField.ReadProgress => query.HasReadingProgress(true, statement.Comparison, (int) value, userId), + FilterField.Formats => query.HasFormat(true, statement.Comparison, (IList) value), + FilterField.ReleaseYear => query.HasReleaseYear(true, statement.Comparison, (int) value), + FilterField.ReadTime => query.HasAverageReadTime(true, statement.Comparison, (int) value), + _ => throw new ArgumentOutOfRangeException() + }; + } + private async Task> CreateFilteredSearchQueryable(int userId, int libraryId, FilterDto filter, IQueryable sQuery) { var userLibraries = await GetUserLibrariesForFilteredQuery(libraryId, userId, QueryContext.Search); @@ -919,41 +1095,10 @@ private async Task> CreateFilteredSearchQueryable(int userId, || EF.Functions.Like(s.LocalizedName!, $"%{filter.SeriesNameQuery}%")) .Where(s => userLibraries.Contains(s.LibraryId) && formats.Contains(s.Format)) + .Sort(filter.SortOptions) .AsNoTracking(); - // If no sort options, default to using SortName - filter.SortOptions ??= new SortOptions() - { - IsAscending = true, - SortField = SortField.SortName - }; - - if (filter.SortOptions.IsAscending) - { - query = filter.SortOptions.SortField switch - { - SortField.SortName => query.OrderBy(s => s.SortName!.ToLower()), - SortField.CreatedDate => query.OrderBy(s => s.Created), - SortField.LastModifiedDate => query.OrderBy(s => s.LastModified), - SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded), - SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead), - _ => query - }; - } - else - { - query = filter.SortOptions.SortField switch - { - SortField.SortName => query.OrderByDescending(s => s.SortName!.ToLower()), - SortField.CreatedDate => query.OrderByDescending(s => s.Created), - SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified), - SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded), - SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead), - _ => query - }; - } - - return query; + return query.AsSplitQuery(); } public async Task GetSeriesMetadata(int seriesId) @@ -1615,6 +1760,7 @@ private async Task> GetRecentlyAddedChaptersQue .AsEnumerable(); } + [Obsolete("Use GetWantToReadForUserV2Async")] public async Task> GetWantToReadForUserAsync(int userId, UserParams userParams, FilterDto filter) { var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync(); @@ -1630,6 +1776,21 @@ public async Task> GetWantToReadForUserAsync(int userId, Us return await PagedList.CreateAsync(filteredQuery.ProjectTo(_mapper.ConfigurationProvider), userParams.PageNumber, userParams.PageSize); } + public async Task> GetWantToReadForUserV2Async(int userId, UserParams userParams, FilterV2Dto filter) + { + var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync(); + var query = _context.AppUser + .Where(user => user.Id == userId) + .SelectMany(u => u.WantToRead) + .Where(s => libraryIds.Contains(s.LibraryId)) + .AsSplitQuery() + .AsNoTracking(); + + var filteredQuery = await CreateFilteredSearchQueryableV2(userId, filter, QueryContext.None, query); + + return await PagedList.CreateAsync(filteredQuery.ProjectTo(_mapper.ConfigurationProvider), userParams.PageNumber, userParams.PageSize); + } + public async Task> GetWantToReadForUserAsync(int userId) { var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync(); diff --git a/API/Data/Repositories/UserRepository.cs b/API/Data/Repositories/UserRepository.cs index 2db9497949..67a72a7a96 100644 --- a/API/Data/Repositories/UserRepository.cs +++ b/API/Data/Repositories/UserRepository.cs @@ -7,12 +7,14 @@ using API.DTOs; using API.DTOs.Account; using API.DTOs.Filtering; +using API.DTOs.Filtering.v2; using API.DTOs.Reader; using API.DTOs.Scrobbling; using API.DTOs.SeriesDetail; using API.Entities; using API.Extensions; using API.Extensions.QueryExtensions; +using API.Extensions.QueryExtensions.Filtering; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Identity; @@ -53,7 +55,7 @@ public interface IUserRepository Task> GetBookmarkDtosForSeries(int userId, int seriesId); Task> GetBookmarkDtosForVolume(int userId, int volumeId); Task> GetBookmarkDtosForChapter(int userId, int chapterId); - Task> GetAllBookmarkDtos(int userId, FilterDto filter); + Task> GetAllBookmarkDtos(int userId, FilterV2Dto filter); Task> GetAllBookmarksAsync(); Task GetBookmarkForPage(int page, int chapterId, int userId); Task GetBookmarkAsync(int bookmarkId); @@ -374,38 +376,92 @@ public async Task> GetBookmarkDtosForChapter(int userId /// /// Only supports SeriesNameQuery /// - public async Task> GetAllBookmarkDtos(int userId, FilterDto filter) + public async Task> GetAllBookmarkDtos(int userId, FilterV2Dto filter) { var query = _context.AppUserBookmark .Where(x => x.AppUserId == userId) .OrderBy(x => x.Created) .AsNoTracking(); - if (string.IsNullOrEmpty(filter.SeriesNameQuery)) - return await query + var filterSeriesQuery = query.Join(_context.Series, b => b.SeriesId, s => s.Id, + (bookmark, series) => new BookmarkSeriesPair() + { + bookmark = bookmark, + series = series + }); + + var filterStatement = filter.Statements.FirstOrDefault(f => f.Field == FilterField.SeriesName); + if (filterStatement == null || string.IsNullOrWhiteSpace(filterStatement.Value)) + { + return await ApplyLimit(filterSeriesQuery + .Sort(filter.SortOptions) + .AsSplitQuery(), filter.LimitTo) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); - - var seriesNameQueryNormalized = filter.SeriesNameQuery.ToNormalized(); - var filterSeriesQuery = query.Join(_context.Series, b => b.SeriesId, s => s.Id, (bookmark, series) => new - { - bookmark, - series - }) - .Where(o => (EF.Functions.Like(o.series.Name, $"%{filter.SeriesNameQuery}%")) - || (o.series.OriginalName != null && EF.Functions.Like(o.series.OriginalName, $"%{filter.SeriesNameQuery}%")) - || (o.series.LocalizedName != null && EF.Functions.Like(o.series.LocalizedName, $"%{filter.SeriesNameQuery}%")) - || (EF.Functions.Like(o.series.NormalizedName, $"%{seriesNameQueryNormalized}%")) - ); - - query = filterSeriesQuery.Select(o => o.bookmark); - - - return await query + } + + var queryString = filterStatement.Value.ToNormalized(); + switch (filterStatement.Comparison) + { + case FilterComparison.Equal: + filterSeriesQuery = filterSeriesQuery.Where(s => s.series.Name.Equals(queryString) + || s.series.OriginalName.Equals(queryString) + || s.series.LocalizedName.Equals(queryString) + || s.series.SortName.Equals(queryString)); + break; + case FilterComparison.BeginsWith: + filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"{queryString}%") + ||EF.Functions.Like(s.series.OriginalName, $"{queryString}%") + || EF.Functions.Like(s.series.LocalizedName, $"{queryString}%") + || EF.Functions.Like(s.series.SortName, $"{queryString}%")); + break; + case FilterComparison.EndsWith: + filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"%{queryString}") + ||EF.Functions.Like(s.series.OriginalName, $"%{queryString}") + || EF.Functions.Like(s.series.LocalizedName, $"%{queryString}") + || EF.Functions.Like(s.series.SortName, $"%{queryString}")); + break; + case FilterComparison.Matches: + filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"%{queryString}%") + ||EF.Functions.Like(s.series.OriginalName, $"%{queryString}%") + || EF.Functions.Like(s.series.LocalizedName, $"%{queryString}%") + || EF.Functions.Like(s.series.SortName, $"%{queryString}%")); + break; + case FilterComparison.NotEqual: + filterSeriesQuery = filterSeriesQuery.Where(s => s.series.Name != queryString + || s.series.OriginalName != queryString + || s.series.LocalizedName != queryString + || s.series.SortName != queryString); + break; + case FilterComparison.MustContains: + case FilterComparison.NotContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Contains: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + default: + break; + } + + return await ApplyLimit(filterSeriesQuery + .Sort(filter.SortOptions) + .AsSplitQuery(), filter.LimitTo) + .Select(o => o.bookmark) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } + private static IQueryable ApplyLimit(IQueryable query, int limit) + { + return limit <= 0 ? query : query.Take(limit); + } + + /// /// Fetches the UserId by API Key. This does not include any extra information /// diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index 076f086cdc..a1941d17f4 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -108,8 +108,9 @@ public static async Task SeedSettings(DataContext context, IDirectoryService dir new() {Key = ServerSettingKey.HostName, Value = string.Empty}, new() {Key = ServerSettingKey.EncodeMediaAs, Value = EncodeFormat.PNG.ToString()}, new() {Key = ServerSettingKey.LicenseKey, Value = string.Empty}, - new() {Key = ServerSettingKey.OnDeckProgressDays, Value = $"{30}"}, - new() {Key = ServerSettingKey.OnDeckUpdateDays, Value = $"{7}"}, + new() {Key = ServerSettingKey.OnDeckProgressDays, Value = "30"}, + new() {Key = ServerSettingKey.OnDeckUpdateDays, Value = "7"}, + new() {Key = ServerSettingKey.CoverImageSize, Value = CoverImageSize.Default.ToString()}, new() { Key = ServerSettingKey.CacheSize, Value = Configuration.DefaultCacheMemory + string.Empty }, // Not used from DB, but DB is sync with appSettings.json diff --git a/API/Entities/Enums/CoverImageSize.cs b/API/Entities/Enums/CoverImageSize.cs new file mode 100644 index 0000000000..d2d0eebb67 --- /dev/null +++ b/API/Entities/Enums/CoverImageSize.cs @@ -0,0 +1,36 @@ +namespace API.Entities.Enums; + +public enum CoverImageSize +{ + /// + /// Default Size: 320x455 (wxh) + /// + Default = 1, + /// + /// 640x909 + /// + Medium = 2, + /// + /// 900x1277 + /// + Large = 3, + /// + /// 1265x1795 + /// + XLarge = 4 +} + +public static class CoverImageSizeExtensions +{ + public static (int Width, int Height) GetDimensions(this CoverImageSize size) + { + return size switch + { + CoverImageSize.Default => (320, 455), + CoverImageSize.Medium => (640, 909), + CoverImageSize.Large => (900, 1277), + CoverImageSize.XLarge => (1265, 1795), + _ => (320, 455) + }; + } +} diff --git a/API/Entities/Enums/ServerSettingKey.cs b/API/Entities/Enums/ServerSettingKey.cs index c8d9c12bee..af699a3d9b 100644 --- a/API/Entities/Enums/ServerSettingKey.cs +++ b/API/Entities/Enums/ServerSettingKey.cs @@ -143,5 +143,10 @@ public enum ServerSettingKey /// [Description("OnDeckUpdateDays")] OnDeckUpdateDays = 26, + /// + /// The size of the cover image thumbnail. Defaults to .Default + /// + [Description("CoverImageSize")] + CoverImageSize = 27 } diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index f6c2844d9b..3bfaf9e102 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -83,6 +83,7 @@ public static void AddApplicationServices(this IServiceCollection services, ICon options.UseInMemory(EasyCacheProfiles.License); options.UseInMemory(EasyCacheProfiles.Library); options.UseInMemory(EasyCacheProfiles.RevokedJwt); + options.UseInMemory(EasyCacheProfiles.Filter); // KavitaPlus stuff options.UseInMemory(EasyCacheProfiles.KavitaPlusReviews); diff --git a/API/Extensions/QueryExtensions/Filtering/BookmarkSort.cs b/API/Extensions/QueryExtensions/Filtering/BookmarkSort.cs new file mode 100644 index 0000000000..d5b1a6d9b9 --- /dev/null +++ b/API/Extensions/QueryExtensions/Filtering/BookmarkSort.cs @@ -0,0 +1,59 @@ +using System.Linq; +using API.DTOs.Filtering; +using API.Entities; + +namespace API.Extensions.QueryExtensions.Filtering; + +public class BookmarkSeriesPair +{ + public AppUserBookmark bookmark { get; set; } + public Series series { get; set; } +} + +public static class BookmarkSort +{ + /// + /// Applies the correct sort based on + /// + /// + /// + /// + public static IQueryable Sort(this IQueryable query, SortOptions? sortOptions) + { + // If no sort options, default to using SortName + sortOptions ??= new SortOptions() + { + IsAscending = true, + SortField = SortField.SortName + }; + + if (sortOptions.IsAscending) + { + query = sortOptions.SortField switch + { + SortField.SortName => query.OrderBy(s => s.series.SortName.ToLower()), + SortField.CreatedDate => query.OrderBy(s => s.series.Created), + SortField.LastModifiedDate => query.OrderBy(s => s.series.LastModified), + SortField.LastChapterAdded => query.OrderBy(s => s.series.LastChapterAdded), + SortField.TimeToRead => query.OrderBy(s => s.series.AvgHoursToRead), + SortField.ReleaseYear => query.OrderBy(s => s.series.Metadata.ReleaseYear), + _ => query + }; + } + else + { + query = sortOptions.SortField switch + { + SortField.SortName => query.OrderByDescending(s => s.series.SortName.ToLower()), + SortField.CreatedDate => query.OrderByDescending(s => s.series.Created), + SortField.LastModifiedDate => query.OrderByDescending(s => s.series.LastModified), + SortField.LastChapterAdded => query.OrderByDescending(s => s.series.LastChapterAdded), + SortField.TimeToRead => query.OrderByDescending(s => s.series.AvgHoursToRead), + SortField.ReleaseYear => query.OrderByDescending(s => s.series.Metadata.ReleaseYear), + _ => query + }; + } + + return query; + } +} diff --git a/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs b/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs new file mode 100644 index 0000000000..b1dfeab1fe --- /dev/null +++ b/API/Extensions/QueryExtensions/Filtering/SeriesFilter.cs @@ -0,0 +1,674 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using API.DTOs.Filtering.v2; +using API.Entities; +using API.Entities.Enums; +using API.Services.Tasks.Scanner.Parser; +using Kavita.Common; +using Microsoft.EntityFrameworkCore; + +namespace API.Extensions.QueryExtensions.Filtering; + +#nullable enable + +public static class SeriesFilter +{ + + public static IQueryable HasLanguage(this IQueryable queryable, bool condition, + FilterComparison comparison, IList languages) + { + if (languages.Count == 0 || !condition) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Metadata.Language.Equals(languages.First())); + case FilterComparison.Contains: + return queryable.Where(s => languages.Contains(s.Metadata.Language)); + case FilterComparison.MustContains: + return queryable.Where(s => languages.All(s2 => s2.Equals(s.Metadata.Language))); + case FilterComparison.NotContains: + return queryable.Where(s => !languages.Contains(s.Metadata.Language)); + case FilterComparison.NotEqual: + return queryable.Where(s => !s.Metadata.Language.Equals(languages.First())); + case FilterComparison.Matches: + return queryable.Where(s => EF.Functions.Like(s.Metadata.Language, $"{languages.First()}%")); + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasReleaseYear(this IQueryable queryable, bool condition, + FilterComparison comparison, int? releaseYear) + { + if (!condition || releaseYear == null) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Metadata.ReleaseYear == releaseYear); + case FilterComparison.GreaterThan: + case FilterComparison.IsAfter: + return queryable.Where(s => s.Metadata.ReleaseYear > releaseYear); + case FilterComparison.GreaterThanEqual: + return queryable.Where(s => s.Metadata.ReleaseYear >= releaseYear); + case FilterComparison.LessThan: + case FilterComparison.IsBefore: + return queryable.Where(s => s.Metadata.ReleaseYear < releaseYear); + case FilterComparison.LessThanEqual: + return queryable.Where(s => s.Metadata.ReleaseYear <= releaseYear); + case FilterComparison.IsInLast: + return queryable.Where(s => s.Metadata.ReleaseYear >= DateTime.Now.Year - (int) releaseYear); + case FilterComparison.IsNotInLast: + return queryable.Where(s => s.Metadata.ReleaseYear < DateTime.Now.Year - (int) releaseYear); + case FilterComparison.Matches: + case FilterComparison.Contains: + case FilterComparison.NotContains: + case FilterComparison.NotEqual: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.ReleaseYear"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + + public static IQueryable HasRating(this IQueryable queryable, bool condition, + FilterComparison comparison, int rating, int userId) + { + if (rating < 0 || !condition || userId <= 0) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Ratings.Any(r => r.Rating == rating && r.AppUserId == userId)); + case FilterComparison.GreaterThan: + return queryable.Where(s => s.Ratings.Any(r => r.Rating > rating && r.AppUserId == userId)); + case FilterComparison.GreaterThanEqual: + return queryable.Where(s => s.Ratings.Any(r => r.Rating >= rating && r.AppUserId == userId)); + case FilterComparison.LessThan: + return queryable.Where(s => s.Ratings.Any(r => r.Rating < rating && r.AppUserId == userId)); + case FilterComparison.LessThanEqual: + return queryable.Where(s => s.Ratings.Any(r => r.Rating <= rating && r.AppUserId == userId)); + case FilterComparison.Contains: + case FilterComparison.Matches: + case FilterComparison.NotContains: + case FilterComparison.NotEqual: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.Rating"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasAgeRating(this IQueryable queryable, bool condition, + FilterComparison comparison, IList ratings) + { + if (!condition || ratings.Count == 0) return queryable; + + var firstRating = ratings.First(); + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Metadata.AgeRating == firstRating); + case FilterComparison.GreaterThan: + return queryable.Where(s => s.Metadata.AgeRating > firstRating); + case FilterComparison.GreaterThanEqual: + return queryable.Where(s => s.Metadata.AgeRating >= firstRating); + case FilterComparison.LessThan: + return queryable.Where(s => s.Metadata.AgeRating < firstRating); + case FilterComparison.LessThanEqual: + return queryable.Where(s => s.Metadata.AgeRating <= firstRating); + case FilterComparison.Contains: + return queryable.Where(s => ratings.Contains(s.Metadata.AgeRating)); + case FilterComparison.NotContains: + return queryable.Where(s => !ratings.Contains(s.Metadata.AgeRating)); + case FilterComparison.NotEqual: + return queryable.Where(s => s.Metadata.AgeRating != firstRating); + case FilterComparison.Matches: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.AgeRating"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + public static IQueryable HasAverageReadTime(this IQueryable queryable, bool condition, + FilterComparison comparison, int avgReadTime) + { + if (!condition || avgReadTime < 0) return queryable; + + switch (comparison) + { + case FilterComparison.NotEqual: + return queryable.Where(s => s.AvgHoursToRead != avgReadTime); + case FilterComparison.Equal: + return queryable.Where(s => s.AvgHoursToRead == avgReadTime); + case FilterComparison.GreaterThan: + return queryable.Where(s => s.AvgHoursToRead > avgReadTime); + case FilterComparison.GreaterThanEqual: + return queryable.Where(s => s.AvgHoursToRead >= avgReadTime); + case FilterComparison.LessThan: + return queryable.Where(s => s.AvgHoursToRead < avgReadTime); + case FilterComparison.LessThanEqual: + return queryable.Where(s => s.AvgHoursToRead <= avgReadTime); + case FilterComparison.Contains: + case FilterComparison.Matches: + case FilterComparison.NotContains: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.AverageReadTime"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasPublicationStatus(this IQueryable queryable, bool condition, + FilterComparison comparison, IList pubStatues) + { + if (!condition || pubStatues.Count == 0) return queryable; + + var firstStatus = pubStatues.First(); + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Metadata.PublicationStatus == firstStatus); + case FilterComparison.Contains: + return queryable.Where(s => pubStatues.Contains(s.Metadata.PublicationStatus)); + case FilterComparison.NotContains: + return queryable.Where(s => !pubStatues.Contains(s.Metadata.PublicationStatus)); + case FilterComparison.NotEqual: + return queryable.Where(s => s.Metadata.PublicationStatus != firstStatus); + case FilterComparison.MustContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.Matches: + throw new KavitaException($"{comparison} not applicable for Series.PublicationStatus"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + /// + /// + /// + /// This is more taxing on memory as the percentage calculation must be done in Memory + /// + /// + public static IQueryable HasReadingProgress(this IQueryable queryable, bool condition, + FilterComparison comparison, int readProgress, int userId) + { + if (!condition) return queryable; + + var subQuery = queryable + .Include(s => s.Progress) + .Where(s => s.Progress != null) + .Select(s => new + { + Series = s, + Percentage = Math.Truncate(((double) s.Progress + .Where(p => p != null && p.AppUserId == userId) + .Sum(p => p != null ? (p.PagesRead * 1.0f / s.Pages) : 0) * 100)) + }) + .AsEnumerable(); + + switch (comparison) + { + case FilterComparison.Equal: + subQuery = subQuery.Where(s => s.Percentage == readProgress); + break; + case FilterComparison.GreaterThan: + subQuery = subQuery.Where(s => s.Percentage > readProgress); + break; + case FilterComparison.GreaterThanEqual: + subQuery = subQuery.Where(s => s.Percentage >= readProgress); + break; + case FilterComparison.LessThan: + subQuery = subQuery.Where(s => s.Percentage < readProgress); + break; + case FilterComparison.LessThanEqual: + subQuery = subQuery.Where(s => s.Percentage <= readProgress); + break; + case FilterComparison.NotEqual: + subQuery = subQuery.Where(s => s.Percentage != readProgress); + break; + case FilterComparison.Matches: + case FilterComparison.Contains: + case FilterComparison.NotContains: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.ReadProgress"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + + var ids = subQuery.Select(s => s.Series.Id).ToList(); + return queryable.Where(s => ids.Contains(s.Id)); + } + + public static IQueryable HasTags(this IQueryable queryable, bool condition, + FilterComparison comparison, IList tags) + { + if (!condition || tags.Count == 0) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + case FilterComparison.Contains: + return queryable.Where(s => s.Metadata.Tags.Any(t => tags.Contains(t.Id))); + case FilterComparison.NotEqual: + case FilterComparison.NotContains: + return queryable.Where(s => s.Metadata.Tags.Any(t => !tags.Contains(t.Id))); + case FilterComparison.MustContains: + // Deconstruct and do a Union of a bunch of where statements since this doesn't translate + var queries = new List>() + { + queryable + }; + queries.AddRange(tags.Select(gId => queryable.Where(s => s.Metadata.Tags.Any(p => p.Id == gId)))); + + return queries.Aggregate((q1, q2) => q1.Intersect(q2)); + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Matches: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + throw new KavitaException($"{comparison} not applicable for Series.Tags"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasPeople(this IQueryable queryable, bool condition, + FilterComparison comparison, IList people) + { + if (!condition || people.Count == 0) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + case FilterComparison.Contains: + return queryable.Where(s => s.Metadata.People.Any(p => people.Contains(p.Id))); + case FilterComparison.NotEqual: + case FilterComparison.NotContains: + return queryable.Where(s => s.Metadata.People.Any(t => !people.Contains(t.Id))); + case FilterComparison.MustContains: + // Deconstruct and do a Union of a bunch of where statements since this doesn't translate + var queries = new List>() + { + queryable + }; + queries.AddRange(people.Select(gId => queryable.Where(s => s.Metadata.People.Any(p => p.Id == gId)))); + + return queries.Aggregate((q1, q2) => q1.Intersect(q2)); + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.Matches: + throw new KavitaException($"{comparison} not applicable for Series.People"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasGenre(this IQueryable queryable, bool condition, + FilterComparison comparison, IList genres) + { + if (!condition || genres.Count == 0) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + case FilterComparison.Contains: + return queryable.Where(s => s.Metadata.Genres.Any(p => genres.Contains(p.Id))); + case FilterComparison.NotEqual: + case FilterComparison.NotContains: + return queryable.Where(s => s.Metadata.Genres.All(p => !genres.Contains(p.Id))); + case FilterComparison.MustContains: + // Deconstruct and do a Union of a bunch of where statements since this doesn't translate + var queries = new List>() + { + queryable + }; + queries.AddRange(genres.Select(gId => queryable.Where(s => s.Metadata.Genres.Any(p => p.Id == gId)))); + + return queries.Aggregate((q1, q2) => q1.Intersect(q2)); + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Matches: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + throw new KavitaException($"{comparison} not applicable for Series.Genres"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasFormat(this IQueryable queryable, bool condition, + FilterComparison comparison, IList formats) + { + if (!condition || formats.Count == 0) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + case FilterComparison.Contains: + return queryable.Where(s => formats.Contains(s.Format)); + case FilterComparison.NotContains: + case FilterComparison.NotEqual: + return queryable.Where(s => !formats.Contains(s.Format)); + case FilterComparison.MustContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Matches: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + throw new KavitaException($"{comparison} not applicable for Series.Format"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasCollectionTags(this IQueryable queryable, bool condition, + FilterComparison comparison, IList collectionTags) + { + if (!condition || collectionTags.Count == 0) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + case FilterComparison.Contains: + return queryable.Where(s => s.Metadata.CollectionTags.Any(t => collectionTags.Contains(t.Id))); + case FilterComparison.NotContains: + case FilterComparison.NotEqual: + return queryable.Where(s => !s.Metadata.CollectionTags.Any(t => collectionTags.Contains(t.Id))); + case FilterComparison.MustContains: + // Deconstruct and do a Union of a bunch of where statements since this doesn't translate + var queries = new List>() + { + queryable + }; + queries.AddRange(collectionTags.Select(gId => queryable.Where(s => s.Metadata.CollectionTags.Any(p => p.Id == gId)))); + + return queries.Aggregate((q1, q2) => q1.Intersect(q2)); + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Matches: + case FilterComparison.BeginsWith: + case FilterComparison.EndsWith: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + throw new KavitaException($"{comparison} not applicable for Series.CollectionTags"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null); + } + } + + public static IQueryable HasName(this IQueryable queryable, bool condition, + FilterComparison comparison, string queryString) + { + if (string.IsNullOrEmpty(queryString) || !condition) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Name.Equals(queryString) + || s.OriginalName.Equals(queryString) + || s.LocalizedName.Equals(queryString) + || s.SortName.Equals(queryString)); + case FilterComparison.BeginsWith: + return queryable.Where(s => EF.Functions.Like(s.Name, $"{queryString}%") + ||EF.Functions.Like(s.OriginalName, $"{queryString}%") + || EF.Functions.Like(s.LocalizedName, $"{queryString}%") + || EF.Functions.Like(s.SortName, $"{queryString}%")); + case FilterComparison.EndsWith: + return queryable.Where(s => EF.Functions.Like(s.Name, $"%{queryString}") + ||EF.Functions.Like(s.OriginalName, $"%{queryString}") + || EF.Functions.Like(s.LocalizedName, $"%{queryString}") + || EF.Functions.Like(s.SortName, $"%{queryString}")); + case FilterComparison.Matches: + return queryable.Where(s => EF.Functions.Like(s.Name, $"%{queryString}%") + ||EF.Functions.Like(s.OriginalName, $"%{queryString}%") + || EF.Functions.Like(s.LocalizedName, $"%{queryString}%") + || EF.Functions.Like(s.SortName, $"%{queryString}%")); + case FilterComparison.NotEqual: + return queryable.Where(s => s.Name != queryString + || s.OriginalName != queryString + || s.LocalizedName != queryString + || s.SortName != queryString); + case FilterComparison.NotContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Contains: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.Name"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported"); + } + } + + public static IQueryable HasSummary(this IQueryable queryable, bool condition, + FilterComparison comparison, string queryString) + { + if (!condition) return queryable; + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.Metadata.Summary.Equals(queryString)); + case FilterComparison.BeginsWith: + return queryable.Where(s => EF.Functions.Like(s.Metadata.Summary, $"{queryString}%")); + case FilterComparison.EndsWith: + return queryable.Where(s => EF.Functions.Like(s.Metadata.Summary, $"%{queryString}")); + case FilterComparison.Matches: + return queryable.Where(s => EF.Functions.Like(s.Metadata.Summary, $"%{queryString}%")); + case FilterComparison.NotEqual: + return queryable.Where(s => s.Metadata.Summary != queryString); + case FilterComparison.NotContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Contains: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.Metadata.Summary"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported"); + } + } + + public static IQueryable HasPath(this IQueryable queryable, bool condition, + FilterComparison comparison, string queryString) + { + if (!condition) return queryable; + + var normalizedPath = Parser.NormalizePath(queryString); + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => s.FolderPath != null && s.FolderPath.Equals(normalizedPath)); + case FilterComparison.BeginsWith: + return queryable.Where(s => s.FolderPath != null && EF.Functions.Like(s.FolderPath, $"{normalizedPath}%")); + case FilterComparison.EndsWith: + return queryable.Where(s => s.FolderPath != null && EF.Functions.Like(s.FolderPath, $"%{normalizedPath}")); + case FilterComparison.Matches: + return queryable.Where(s => s.FolderPath != null && EF.Functions.Like(s.FolderPath, $"%{normalizedPath}%")); + case FilterComparison.NotEqual: + return queryable.Where(s => s.FolderPath != null && s.FolderPath != normalizedPath); + case FilterComparison.NotContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Contains: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.FolderPath"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported"); + } + } + + public static IQueryable HasFilePath(this IQueryable queryable, bool condition, + FilterComparison comparison, string queryString) + { + if (!condition) return queryable; + + var normalizedPath = Parser.NormalizePath(queryString); + + switch (comparison) + { + case FilterComparison.Equal: + return queryable.Where(s => + s.Volumes.Any(v => + v.Chapters.Any(c => + c.Files.Any(f => + f.FilePath != null && f.FilePath.Equals(normalizedPath) + ) + ) + ) + ); + case FilterComparison.BeginsWith: + return queryable.Where(s => + s.Volumes.Any(v => + v.Chapters.Any(c => + c.Files.Any(f => + f.FilePath != null && EF.Functions.Like(f.FilePath, $"{normalizedPath}%") + ) + ) + ) + ); + case FilterComparison.EndsWith: + return queryable.Where(s => + s.Volumes.Any(v => + v.Chapters.Any(c => + c.Files.Any(f => + f.FilePath != null && EF.Functions.Like(f.FilePath, $"%{normalizedPath}") + ) + ) + ) + ); + case FilterComparison.Matches: + return queryable.Where(s => + s.Volumes.Any(v => + v.Chapters.Any(c => + c.Files.Any(f => + f.FilePath != null && EF.Functions.Like(f.FilePath, $"%{normalizedPath}%") + ) + ) + ) + ); + case FilterComparison.NotEqual: + return queryable.Where(s => + s.Volumes.Any(v => + v.Chapters.Any(c => + c.Files.Any(f => + f.FilePath == null || !f.FilePath.Equals(normalizedPath) + ) + ) + ) + ); + case FilterComparison.NotContains: + case FilterComparison.GreaterThan: + case FilterComparison.GreaterThanEqual: + case FilterComparison.LessThan: + case FilterComparison.LessThanEqual: + case FilterComparison.Contains: + case FilterComparison.IsBefore: + case FilterComparison.IsAfter: + case FilterComparison.IsInLast: + case FilterComparison.IsNotInLast: + case FilterComparison.MustContains: + throw new KavitaException($"{comparison} not applicable for Series.FolderPath"); + default: + throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported"); + } + } + + +} diff --git a/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs b/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs new file mode 100644 index 0000000000..ac075fc219 --- /dev/null +++ b/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs @@ -0,0 +1,53 @@ +using System.Linq; +using API.DTOs.Filtering; +using API.Entities; + +namespace API.Extensions.QueryExtensions.Filtering; + +public static class SeriesSort +{ + /// + /// Applies the correct sort based on + /// + /// + /// + /// + public static IQueryable Sort(this IQueryable query, SortOptions? sortOptions) + { + // If no sort options, default to using SortName + sortOptions ??= new SortOptions() + { + IsAscending = true, + SortField = SortField.SortName + }; + + if (sortOptions.IsAscending) + { + query = sortOptions.SortField switch + { + SortField.SortName => query.OrderBy(s => s.SortName.ToLower()), + SortField.CreatedDate => query.OrderBy(s => s.Created), + SortField.LastModifiedDate => query.OrderBy(s => s.LastModified), + SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded), + SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead), + SortField.ReleaseYear => query.OrderBy(s => s.Metadata.ReleaseYear), + _ => query + }; + } + else + { + query = sortOptions.SortField switch + { + SortField.SortName => query.OrderByDescending(s => s.SortName.ToLower()), + SortField.CreatedDate => query.OrderByDescending(s => s.Created), + SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified), + SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded), + SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead), + SortField.ReleaseYear => query.OrderByDescending(s => s.Metadata.ReleaseYear), + _ => query + }; + } + + return query; + } +} diff --git a/API/Extensions/QueryExtensions/QueryableExtensions.cs b/API/Extensions/QueryExtensions/QueryableExtensions.cs index f25ea12f00..c01297d3ec 100644 --- a/API/Extensions/QueryExtensions/QueryableExtensions.cs +++ b/API/Extensions/QueryExtensions/QueryableExtensions.cs @@ -110,6 +110,55 @@ public static IQueryable WhereIf(this IQueryable queryable, bool condit return condition ? queryable.Where(predicate) : queryable; } + public static IQueryable WhereLike(this IQueryable queryable, bool condition, Expression> propertySelector, string searchQuery) + where T : class + { + if (!condition || string.IsNullOrEmpty(searchQuery)) return queryable; + + var method = typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string) }); + var dbFunctions = typeof(EF).GetMethod(nameof(EF.Functions))?.Invoke(null, null); + var searchExpression = Expression.Constant($"%{searchQuery}%"); + var likeExpression = Expression.Call(method, Expression.Constant(dbFunctions), propertySelector.Body, searchExpression); + var lambda = Expression.Lambda>(likeExpression, propertySelector.Parameters[0]); + + return queryable.Where(lambda); + } + + /// + /// Performs a WhereLike that ORs multiple fields + /// + /// + /// + /// + /// + /// + /// + public static IQueryable WhereLike(this IQueryable queryable, bool condition, List>> propertySelectors, string searchQuery) + where T : class + { + if (!condition || string.IsNullOrEmpty(searchQuery)) return queryable; + + var method = typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string) }); + var dbFunctions = typeof(EF).GetMethod(nameof(EF.Functions))?.Invoke(null, null); + var searchExpression = Expression.Constant($"%{searchQuery}%"); + + Expression orExpression = null; + foreach (var propertySelector in propertySelectors) + { + var likeExpression = Expression.Call(method, Expression.Constant(dbFunctions), propertySelector.Body, searchExpression); + var lambda = Expression.Lambda>(likeExpression, propertySelector.Parameters[0]); + orExpression = orExpression == null ? lambda.Body : Expression.OrElse(orExpression, lambda.Body); + } + + if (orExpression == null) + { + throw new ArgumentNullException(nameof(orExpression)); + } + + var combinedLambda = Expression.Lambda>(orExpression, propertySelectors[0].Parameters[0]); + return queryable.Where(combinedLambda); + } + public static IQueryable SortBy(this IQueryable query, ScrobbleEventSortField sort, bool isDesc = false) { if (isDesc) diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index c42a09eff2..4c5a1b4f2f 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -18,6 +18,7 @@ using API.Entities.Enums; using API.Entities.Metadata; using API.Entities.Scrobble; +using API.Extensions.QueryExtensions.Filtering; using API.Helpers.Converters; using AutoMapper; using CollectionTag = API.Entities.CollectionTag; @@ -31,6 +32,13 @@ public class AutoMapperProfiles : Profile { public AutoMapperProfiles() { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.bookmark.Id)) + .ForMember(dest => dest.Page, opt => opt.MapFrom(src => src.bookmark.Page)) + .ForMember(dest => dest.VolumeId, opt => opt.MapFrom(src => src.bookmark.VolumeId)) + .ForMember(dest => dest.SeriesId, opt => opt.MapFrom(src => src.bookmark.SeriesId)) + .ForMember(dest => dest.ChapterId, opt => opt.MapFrom(src => src.bookmark.ChapterId)) + .ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.series)); CreateMap(); CreateMap(); CreateMap(); diff --git a/API/Helpers/Converters/FilterFieldValueConverter.cs b/API/Helpers/Converters/FilterFieldValueConverter.cs new file mode 100644 index 0000000000..36ab4913b8 --- /dev/null +++ b/API/Helpers/Converters/FilterFieldValueConverter.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using API.DTOs.Filtering.v2; +using API.Entities.Enums; + +namespace API.Helpers.Converters; + +public static class FilterFieldValueConverter +{ + public static (object Value, Type Type) ConvertValue(FilterField field, string value) + { + return field switch + { + FilterField.SeriesName => (value, typeof(string)), + FilterField.Path => (value, typeof(string)), + FilterField.FilePath => (value, typeof(string)), + FilterField.ReleaseYear => (int.Parse(value), typeof(int)), + FilterField.Languages => (value.Split(',').ToList(), typeof(IList)), + FilterField.PublicationStatus => (value.Split(',') + .Select(x => (PublicationStatus) Enum.Parse(typeof(PublicationStatus), x)) + .ToList(), typeof(IList)), + FilterField.Summary => (value, typeof(string)), + FilterField.AgeRating => (value.Split(',') + .Select(x => (AgeRating) Enum.Parse(typeof(AgeRating), x)) + .ToList(), typeof(IList)), + FilterField.UserRating => (int.Parse(value), typeof(int)), + FilterField.Tags => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.CollectionTags => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Translators => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Characters => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Publisher => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Editor => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.CoverArtist => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Letterer => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Colorist => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Inker => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Penciller => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Writers => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Genres => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.Libraries => (value.Split(',') + .Select(int.Parse) + .ToList(), typeof(IList)), + FilterField.ReadProgress => (int.Parse(value), typeof(int)), + FilterField.Formats => (value.Split(',') + .Select(x => (MangaFormat) Enum.Parse(typeof(MangaFormat), x)) + .ToList(), typeof(IList)), + FilterField.ReadTime => (int.Parse(value), typeof(int)), + _ => throw new ArgumentException("Invalid field type") + }; + } +} diff --git a/API/Helpers/Converters/ServerSettingConverter.cs b/API/Helpers/Converters/ServerSettingConverter.cs index 9163a027f4..a55e104a76 100644 --- a/API/Helpers/Converters/ServerSettingConverter.cs +++ b/API/Helpers/Converters/ServerSettingConverter.cs @@ -79,6 +79,9 @@ public ServerSettingDto Convert(IEnumerable source, ServerSetting case ServerSettingKey.OnDeckUpdateDays: destination.OnDeckUpdateDays = int.Parse(row.Value); break; + case ServerSettingKey.CoverImageSize: + destination.CoverImageSize = Enum.Parse(row.Value); + break; } } diff --git a/API/I18N/cs.json b/API/I18N/cs.json new file mode 100644 index 0000000000..79b614c258 --- /dev/null +++ b/API/I18N/cs.json @@ -0,0 +1,163 @@ +{ + "password-updated": "Heslo aktualizováno", + "reading-list-updated": "Aktualizováno", + "confirm-email": "Musíte potvrdit svůj e-mail", + "locked-out": "Byli jste zablokováni z příliš mnoha pokusů o autorizaci. Počkejte prosím 10 minut.", + "disabled-account": "Váš účet je deaktivován. Kontaktujte správce serveru.", + "register-user": "Při registraci uživatele se něco pokazilo", + "validate-email": "Při ověřování vašeho e-mailu došlo k problému: {0}", + "confirm-token-gen": "Při generování potvrzovacího tokenu došlo k problému", + "denied": "Nepovoleno", + "permission-denied": "K této operaci nemáte oprávnění", + "password-required": "Chcete-li změnit svůj účet a nejste správce, musíte zadat své stávající platné heslo", + "unable-to-reset-key": "Něco se pokazilo, nelze resetovat klíč", + "invalid-payload": "Neplatný náklad", + "nothing-to-do": "Není co dělat", + "share-multiple-emails": "Nemůžete sdílet e-maily mezi více účty", + "generate-token": "Při generování potvrzovacího e-mailového tokenu došlo k problému. Viz protokoly", + "age-restriction-update": "Při aktualizaci věkového omezení došlo k chybě", + "no-user": "Uživatel neexistuje", + "username-taken": "Uživatelské jméno je již používáno", + "bad-credentials": "Vaše přihlašovací údaje nejsou správné", + "invalid-password": "Neplatné heslo", + "invalid-token": "Neplatný token", + "user-already-confirmed": "Uživatel je již potvrzen", + "manual-setup-fail": "Ruční nastavení nelze dokončit. Zrušte a znovu vytvořte pozvánku", + "user-already-registered": "Uživatel je již registrován jako {0}", + "user-already-invited": "Uživatel je již pozván pod tímto e-mailem a dosud pozvánku nepřijal.", + "generic-invite-user": "Při pozvání uživatele došlo k problému. Zkontrolujte protokoly.", + "invalid-email-confirmation": "Neplatné potvrzení e-mailem", + "generic-user-email-update": "Nelze aktualizovat e-mail pro uživatele. Zkontrolujte protokoly.", + "generic-password-update": "Při potvrzování nového hesla došlo k neočekávané chybě", + "name-required": "Název nemůže být prázdný", + "duplicate-bookmark": "Duplicitní položka záložky již existuje", + "reading-list-permission": "K tomuto seznamu čtení nemáte oprávnění nebo seznam neexistuje", + "reading-list-item-delete": "Položku(y) se nepodařilo smazat", + "reading-list-deleted": "Seznam četby byl smazán", + "generic-reading-list-delete": "Při mazání seznamu četby došlo k problému", + "forgot-password-generic": "Na e-mail bude zaslán e-mail, pokud existuje v naší databázi", + "email-sent": "Email odeslán", + "user-migration-needed": "Tento uživatel potřebuje migrovat. Požádejte je, aby se odhlásili a přihlásili, aby spustili migrační tok", + "generic-user-update": "Při aktualizaci uživatele došlo k výjimce", + "valid-number": "Musí být platné číslo stránky", + "reading-list-position": "Pozici se nepodařilo aktualizovat", + "not-accessible-password": "Váš server není přístupný. Odkaz na resetování hesla je v protokolech", + "not-accessible": "Váš server není přístupný externě", + "generic-invite-email": "Při opětovném odesílání e-mailu s pozvánkou došlo k problému", + "chapter-doesnt-exist": "Kapitola neexistuje", + "collection-doesnt-exist": "Sbírka neexistuje", + "device-doesnt-exist": "Zařízení neexistuje", + "generic-device-update": "Při aktualizaci zařízení došlo k chybě", + "generic-device-delete": "Při mazání zařízení došlo k chybě", + "greater-0": "{0} musí být větší než 0", + "send-to-kavita-email": "Odeslat do zařízení nelze použít s e-mailovou službou Kavita. Nakonfigurujte si prosím vlastní.", + "send-to-device-status": "Přenos souborů do vašeho zařízení", + "generic-send-to": "Při odesílání souborů do zařízení došlo k chybě", + "admin-already-exists": "Správce již existuje", + "file-missing": "Soubor nebyl v knize nalezen", + "generic-device-create": "Při vytváření zařízení došlo k chybě", + "collection-updated": "Sbírka byla úspěšně aktualizována", + "series-doesnt-exist": "Série neexistuje", + "must-be-defined": "{0} musí být definováno", + "file-doesnt-exist": "Soubor neexistuje", + "library-name-exists": "Název knihovny již existuje. Zvolte prosím jedinečný název serveru.", + "no-library-access": "Uživatel nemá přístup k této knihovně", + "user-doesnt-exist": "Uživatel neexistuje", + "library-doesnt-exist": "Knihovna neexistuje", + "invalid-path": "Neplatná cesta", + "delete-library-while-scan": "Knihovnu nelze odstranit, když probíhá skenování. Počkejte prosím na dokončení skenování nebo restartujte Kavitu a poté zkuste smazat", + "generic-library-update": "Při aktualizaci knihovny došlo ke kritickému problému.", + "invalid-access": "Neplatný přístup", + "no-image-for-page": "Pro stránku {0} takový obrázek neexistuje. Zkuste obnovit, abyste umožnili opětovné načtení do mezipaměti.", + "perform-scan": "Proveďte prosím skenování této série nebo knihovny a zkuste to znovu", + "generic-read-progress": "Při ukládání postupu došlo k problému", + "bookmark-permission": "Nemáte oprávnění k vytvoření/zrušení záložky", + "cache-file-find": "Nelze najít obrázek uložený v mezipaměti. Znovu načtěte a zkuste to znovu.", + "generic-reading-list-update": "Při aktualizaci seznamu čtení došlo k problému", + "generic-reading-list-create": "Při vytváření seznamu četby došlo k problému", + "reading-list-doesnt-exist": "Seznam četby neexistuje", + "series-restricted": "Uživatel nemá přístup k této sérii", + "generic-scrobble-hold": "Při přidávání blokování došlo k chybě", + "no-series-collection": "Série pro kolekci se nepodařilo získat", + "generic-series-update": "Při aktualizaci série došlo k chybě", + "update-metadata-fail": "Metadata nelze aktualizovat", + "age-restriction-not-applicable": "Bez omezení", + "generic-relationship": "Při aktualizaci vztahů došlo k problému", + "invalid-username": "Neplatné uživatelské jméno", + "critical-email-migration": "Během migrace e-mailu došlo k problému. Kontaktujte podporu", + "generic-error": "Něco se pokazilo. Prosím zkuste to znovu", + "volume-doesnt-exist": "Svazek neexistuje", + "no-cover-image": "Žádný titulní obrázek", + "bookmark-doesnt-exist": "Záložka neexistuje", + "generic-favicon": "Při načítání faviconu pro doménu došlo k problému", + "generic-library": "Došlo ke kritickému problému. Prosím zkuste to znovu.", + "pdf-doesnt-exist": "PDF neexistuje, když by mělo", + "generic-clear-bookmarks": "Záložky se nepodařilo vymazat", + "bookmark-save": "Záložku se nepodařilo uložit", + "libraries-restricted": "Uživatel nemá přístup k žádným knihovnám", + "no-series": "Série pro knihovnu nelze získat", + "generic-series-delete": "Při mazání série došlo k problému", + "series-updated": "Úspěšně aktualizováno", + "bookmarks-empty": "Záložky nemohou být prázdné", + "invalid-filename": "Neplatný název souboru", + "job-already-running": "Práce již běží", + "encode-as-warning": "Nelze převést na PNG. Pro obaly použijte Refresh Covers. Záložky a oblíbené ikony nelze zpětně kódovat.", + "ip-address-invalid": "IP adresa '{0}' je neplatná", + "bookmark-dir-permissions": "Adresář záložek nemá správná oprávnění k použití pro Kavitu", + "total-backups": "Celkový počet záloh musí být mezi 1 a 30", + "reset-chapter-lock": "Nelze resetovat zámek obalu pro Kapitolu", + "generic-user-delete": "Uživatele se nepodařilo smazat", + "generic-user-pref": "Při ukládání předvoleb došlo k problému", + "opds-disabled": "OPDS není na tomto serveru povoleno", + "on-deck": "Na palubě", + "browse-on-deck": "Procházet na palubě", + "recently-added": "Nedávno přidané", + "want-to-read": "Chci číst", + "browse-recently-added": "Procházet naposledy přidané", + "reading-lists": "Seznamy četby", + "browse-reading-lists": "Procházet podle seznamů četby", + "libraries": "Všechny knihovny", + "browse-libraries": "Procházet podle knihoven", + "collections": "Všechny sbírky", + "browse-collections": "Procházet podle sbírek", + "reading-list-restricted": "Seznam četby neexistuje nebo k němu nemáte přístup", + "query-required": "Musíte předat parametr dotazu", + "search": "Vyhledávání", + "search-description": "Vyhledávejte série, sbírky nebo seznamy četby", + "favicon-doesnt-exist": "Favicon neexistuje", + "not-authenticated": "Uživatel není ověřen", + "unable-to-register-k+": "Licenci nelze zaregistrovat kvůli chybě. Obraťte se na podporu Kavita+", + "anilist-cred-expired": "Přihlašovací údaje AniList vypršely nebo nejsou nastaveny", + "scrobble-bad-payload": "Špatné užitečné zatížení od poskytovatele Scrobble", + "theme-doesnt-exist": "Soubor motivu chybí nebo je neplatný", + "generic-create-temp-archive": "Při vytváření dočasného archivu došlo k problému", + "epub-malformed": "Soubor je poškozen! Nelze přečíst.", + "epub-html-missing": "Nelze najít vhodný html pro tuto stránku", + "collection-tag-title-required": "Název sbírky nemůže být prázdný", + "collection-tag-duplicate": "Sbírka s tímto názvem již existuje", + "device-duplicate": "Zařízení s tímto názvem již existuje", + "device-not-created": "Toto zařízení zatím neexistuje. Nejprve prosím vytvořte", + "progress-must-exist": "U uživatele musí existovat pokrok", + "reading-list-name-exists": "Seznam čtení s tímto názvem již existuje", + "user-no-access-library-from-series": "Uživatel nemá přístup do knihovny, do které tato série patří", + "volume-num": "Svazek {0}", + "book-num": "Kniha {0}", + "issue-num": "Vydání {0}{1}", + "chapter-num": "Kapitola {0}", + "total-logs": "Celkový počet protokolů musí být mezi 1 a 30", + "stats-permission-denied": "Nemáte oprávnění prohlížet statistiky jiného uživatele", + "url-not-valid": "Adresa URL nevrací platný obrázek nebo vyžaduje autorizaci", + "url-required": "Chcete-li použít, musíte předat adresu URL", + "generic-cover-series-save": "Titulní obrázek nelze uložit do Série", + "generic-cover-collection-save": "Titulní obrázek nelze uložit do sbírky", + "generic-cover-reading-list-save": "Nelze uložit titulní obrázek do seznamu četby", + "generic-cover-chapter-save": "Nelze uložit titulní obrázek do kapitoly", + "generic-cover-library-save": "Nelze uložit titulní obrázek do knihovny", + "access-denied": "Nemáte přístup", + "browse-want-to-read": "Procházet Chcete si přečíst", + "bad-copy-files-for-download": "Nelze zkopírovat soubory do dočasného stažení archivu adresáře.", + "send-to-permission": "Nelze odeslat non-EPUB nebo PDF do zařízení, která nejsou podporována na Kindle", + "reading-list-title-required": "Název seznamu čtení nemůže být prázdný", + "series-restricted-age-restriction": "Uživatel nemá povoleno sledovat tuto sérii z důvodu věkového omezení", + "collection-deleted": "Sbírka smazána" +} diff --git a/API/I18N/en.json b/API/I18N/en.json index 29babf0bbd..59851fac50 100644 --- a/API/I18N/en.json +++ b/API/I18N/en.json @@ -43,6 +43,7 @@ "file-missing": "File was not found in book", "collection-updated": "Collection updated successfully", + "collection-deleted": "Collection deleted", "generic-error": "Something went wrong, please try again", "collection-doesnt-exist": "Collection does not exist", @@ -140,6 +141,8 @@ "on-deck": "On Deck", "browse-on-deck": "Browse On Deck", "recently-added": "Recently Added", + "want-to-read": "Want to Read", + "browse-want-to-read": "Browse Want to Read", "browse-recently-added": "Browse Recently Added", "reading-lists": "Reading Lists", "browse-reading-lists": "Browse by Reading Lists", diff --git a/API/I18N/es.json b/API/I18N/es.json index bcfe104c72..658cb4db1d 100644 --- a/API/I18N/es.json +++ b/API/I18N/es.json @@ -4,5 +4,160 @@ "disabled-account": "La cuenta está deshabilitada. Contacta con un administrador.", "validate-email": "Ha habido un error al validar el correo: {0}", "locked-out": "Se ha bloqueado el acceso debido a demasiados intentos. Por favor espera 10 minutos.", - "register-user": "Ha ocurrido un error registrando el usuario" + "register-user": "Ha ocurrido un error registrando el usuario", + "denied": "No permitido", + "permission-denied": "No estás autorizado a realizar esta operación", + "password-required": "Debes introducir tu contraseña para cambiar tu cuenta, excepto si eres administrador", + "invalid-password": "Contraseña incorrecta", + "invalid-token": "Token incorrecto", + "unable-to-reset-key": "Algo fue mal, no fue posible reiniciar la clave", + "confirm-token-gen": "Ha habido un problema generando el token de confirmación", + "invalid-payload": "Paquete no válido", + "nothing-to-do": "Nada que hacer", + "share-multiple-emails": "No puedes compartir correos electrónicos entre varias cuentas", + "generate-token": "Ha habido un problema generando un token de confirmación. Comprueba los registros", + "send-to-device-status": "Transfiriendo archivos a tu dispositivo", + "generic-send-to": "Ha ocurrido un error al enviar los archivos a tu dispositivo", + "series-doesnt-exist": "La serie no existe", + "volume-doesnt-exist": "El volumen no existe", + "bookmarks-empty": "Los marcadores no pueden estar vacíos", + "must-be-defined": "{0} debe estar definido", + "invalid-filename": "Nombre de archivo no válido", + "library-name-exists": "El nombre de la biblioteca ya existe. Por favor, elige un nombre único.", + "user-doesnt-exist": "El usuario no existe", + "library-doesnt-exist": "La biblioteca no existe", + "age-restriction-update": "Ha ocurrido un error al actualizar la restricción de edad", + "user-already-confirmed": "El usuario ya está confirmado", + "generic-user-update": "Ha ocurrido una excepción al actualizar el usuario", + "manual-setup-fail": "No se puede completar la configuración manual. Por favor, cancela y vuelve a generar la invitación", + "user-already-registered": "Usuario ya registrado como {0}", + "user-already-invited": "El usuario ya ha recibido una invitación en esta dirección de correo y está pendiente de aceptarla.", + "generic-invite-user": "Ha ocurrido un problema invitando al usuario. Por favor, comprueba el registro.", + "invalid-email-confirmation": "Confirmación de correo electrónico errónea", + "password-updated": "Contraseña Actualizada", + "forgot-password-generic": "Se enviará el correo si la dirección existe en nuestra base de datos", + "generic-device-create": "Ha ocurrido un error al crear el dispositivo", + "greater-0": "{0} debe ser mayor que 0", + "send-to-kavita-email": "Enviar al dispositivo no se puede utilizar con el servicio de correo electrónico de Kavita. Por favor, configura el tuyo propio.", + "no-cover-image": "No hay imagen de portada", + "bookmark-doesnt-exist": "El marcador no existe", + "generic-favicon": "Ha ocurrido un error al obtener el icono para el dominio", + "file-doesnt-exist": "El archivo no existe", + "generic-library": "Ha ocurrido un error fatal. Por favor, inténtalo de nuevo.", + "no-library-access": "El usuario no tiene acceso a esta biblioteca", + "no-user": "El usuario no existe", + "username-taken": "El nombre de usuario ya existe", + "generic-user-email-update": "No ha sido posible actualizar el correo electrónico del usuario. Comprueba los registros.", + "generic-password-update": "Ha ocurrido un error inesperado al confirmar la nueva contraseña", + "not-accessible-password": "Tu servidor no es accesible. El enlace para restablecer tu contraseña se encuentra en el registro", + "not-accessible": "Tu servidor no es accesible desde fuera", + "email-sent": "Correo electrónico enviado", + "user-migration-needed": "El usuario tiene que migrar. Debe cerrar e iniciar sesión para dar inicio al proceso de migración", + "generic-invite-email": "Ha ocurrido un problema al reenviar el correo de invitación", + "admin-already-exists": "El administrador ya existe", + "invalid-username": "Nombre de usuario no válido", + "critical-email-migration": "Ha ocurrido un problema durante la migración de correo electrónico. Contacta con soporte", + "chapter-doesnt-exist": "El Capítulo no existe", + "collection-updated": "Colección actualizada con éxito", + "file-missing": "No se ha encontrado el archivo en el libro", + "generic-error": "Algo fue mal, por favor inténtalo de nuevo", + "collection-doesnt-exist": "La colección no existe", + "device-doesnt-exist": "El dispositivo no existe", + "generic-device-update": "Ha ocurrido un error al actualizar el dispositivo", + "generic-device-delete": "Ha ocurrido un error al eliminar el dispositivo", + "invalid-path": "Ruta no válida", + "generic-library-update": "Hubo un problema crítico al actualizar la biblioteca.", + "pdf-doesnt-exist": "El PDF no existe cuando debería existir", + "invalid-access": "Acceso no válido", + "no-image-for-page": "No existe tal imagen para la página {0}. Intente refrescar para permitir el re-cache.", + "cache-file-find": "No se ha podido encontrar la imagen en caché. Vuelva a cargar la página e inténtelo de nuevo.", + "name-required": "El nombre no puede estar vacío", + "valid-number": "El número de página debe ser válido", + "duplicate-bookmark": "Ya existe un marcador duplicado", + "reading-list-permission": "Usted no tiene permisos en esta lista de lectura o la lista no existe", + "reading-list-position": "No se ha podido actualizar la posición", + "reading-list-updated": "Actualizado", + "generic-reading-list-delete": "Hubo un problema al borrar la lista de lectura", + "generic-reading-list-update": "Hubo un problema al actualizar la lista de lectura", + "reading-list-doesnt-exist": "La lista de lectura no existe", + "libraries-restricted": "El usuario no tiene acceso a ninguna biblioteca", + "no-series": "No se han podido obtener series para la biblioteca", + "generic-series-delete": "Hubo un problema al borrar las series", + "generic-series-update": "Se ha producido un error al actualizar las series", + "series-updated": "Actualizado correctamente", + "update-metadata-fail": "No se han podido actualizar los metadatos", + "generic-relationship": "Hubo un problema al actualizar las relaciones", + "job-already-running": "Trabajo ya en ejecución", + "ip-address-invalid": "La dirección IP '{0}' no es válida", + "bookmark-dir-permissions": "El directorio de marcadores no tiene los permisos correctos para que Kavita pueda utilizarlo", + "total-backups": "El número total de copias de seguridad debe estar entre 1 y 30", + "stats-permission-denied": "No está autorizado a ver las estadísticas de otro usuario", + "url-not-valid": "La url no devuelve una imagen válida o requiere autorización", + "url-required": "Debe pasar una url para usar", + "generic-cover-series-save": "No se puede guardar la imagen de portada en la serie", + "generic-cover-collection-save": "No se puede guardar la imagen de portada en la colección", + "generic-cover-reading-list-save": "No se puede guardar la imagen de portada en la lista de lectura", + "generic-cover-chapter-save": "No se puede guardar la imagen de portada en el capítulo", + "generic-cover-library-save": "No se puede guardar la imagen de portada en la biblioteca", + "generic-user-pref": "Hubo un problema al guardar las preferencias", + "browse-on-deck": "Navegar por el puente", + "recently-added": "Añadido recientemente", + "reading-lists": "Listas de lectura", + "browse-reading-lists": "Navegar por listas de lectura", + "libraries": "Todas las bibliotecas", + "browse-libraries": "Navegar por bibliotecas", + "collections": "Todas las colecciones", + "query-required": "Debe pasar un parámetro de consulta", + "search": "Buscar", + "favicon-doesnt-exist": "El favicon no existe", + "not-authenticated": "El usuario no está autenticado", + "anilist-cred-expired": "Las credenciales de AniList han caducado o no están configuradas", + "scrobble-bad-payload": "Mala carga útil del proveedor de Scrobble", + "theme-doesnt-exist": "Archivo de tema no válido o no existe", + "generic-create-temp-archive": "Hubo un problema al crear un archivo temporal", + "epub-malformed": "¡El archivo está malformado! No se puede leer.", + "book-num": "Libro {0}", + "issue-num": "Incidencia {0}{1}", + "search-description": "Buscar series, colecciones o listas de lectura", + "unable-to-register-k+": "No se ha podido registrar la licencia debido a un error. Póngase en contacto con el servicio de asistencia de Kavita", + "bad-copy-files-for-download": "No se pueden copiar archivos al directorio temporal de descarga de archivos.", + "send-to-permission": "No se puede enviar archivos que no sean EPUB o PDF a dispositivos no compatibles con Kindle", + "progress-must-exist": "El progreso debe existir en el usuario", + "epub-html-missing": "No se ha podido encontrar el HTML apropiado para esa página", + "collection-tag-duplicate": "Ya existe una colección con este nombre", + "device-duplicate": "Ya existe un dispositivo con este nombre", + "collection-tag-title-required": "El título de la colección no puede estar vacío", + "reading-list-title-required": "El título de la lista de lectura no puede estar vacío", + "device-not-created": "Este dispositivo aún no existe. Por favor, créelo primero", + "reading-list-name-exists": "Ya existe una lista de lectura con este nombre", + "user-no-access-library-from-series": "El usuario no tiene acceso a la biblioteca a la que pertenece esta serie", + "series-restricted-age-restriction": "El usuario no puede ver esta serie debido a restricciones de edad", + "volume-num": "Volumen {0}", + "chapter-num": "Capítulo {0}", + "delete-library-while-scan": "No puede eliminar una biblioteca mientras se está realizando una escaneo. Por favor, espere a que finalice el escaneo o reinicie Kavita y luego intente borrar", + "perform-scan": "Por favor, realice un escaneo en esta serie o biblioteca e inténtelo de nuevo", + "generic-read-progress": "Hubo un problema al guardar el progreso", + "generic-clear-bookmarks": "No se pueden limpiar los marcadores", + "bookmark-permission": "Usted no tiene permiso para marcar/desmarcar", + "bookmark-save": "No se ha podido guardar el marcador", + "reading-list-item-delete": "No se ha podido eliminar el/los elemento(s)", + "reading-list-deleted": "Se ha eliminado la lista de lectura", + "generic-reading-list-create": "Hubo un problema al crear la lista de lectura", + "series-restricted": "El usuario no tiene acceso a esta serie", + "generic-scrobble-hold": "Se ha producido un error al añadir la retención", + "age-restriction-not-applicable": "Sin restricciones", + "no-series-collection": "No se han podido obtener series para la colección", + "encode-as-warning": "No se puede convertir a PNG. Para las carátulas, utilice refrescar carátulas. Los marcadores y favicons no se pueden volver a codificar.", + "total-logs": "El número total de registros debe estar comprendido entre 1 y 30", + "on-deck": "En el puente", + "access-denied": "Usted no tiene acceso", + "reset-chapter-lock": "No se puede restablecer el bloqueo de la portada del capítulo", + "generic-user-delete": "No se ha podido eliminar el usuario", + "opds-disabled": "OPDS no está habilitado en este servidor", + "browse-recently-added": "Navegar por los añadidos recientemente", + "browse-collections": "Navegar por colecciones", + "reading-list-restricted": "La lista de lectura no existe o no tiene acceso", + "browse-want-to-read": "Navegar en deseo leer", + "want-to-read": "Deseo leer", + "collection-deleted": "Colección eliminada" } diff --git a/API/I18N/fr.json b/API/I18N/fr.json index a870815009..a58895b4fc 100644 --- a/API/I18N/fr.json +++ b/API/I18N/fr.json @@ -23,5 +23,140 @@ "user-already-registered": "L'usager à déjà été enregistré en tant que {0}", "user-already-invited": "L'usager à déjà été invité avec ce courriel et n'a pas encore accepté l'invitation.", "generic-invite-user": "Une erreur est survenue lors de l'invitation de l'usager. Voir le journal.", - "invalid-email-confirmation": "La confirmation de courriel est invalide" + "invalid-email-confirmation": "La confirmation de courriel est invalide", + "invalid-payload": "Payload invalide", + "manual-setup-fail": "La configuration manuelle est impossible. Veuillez annuler et recréer l'invitation", + "generic-user-email-update": "Impossible de mettre à jour le courriel de l'utilisateur. Veuillez vérifier les logs.", + "generic-password-update": "Une erreur s'est produite lors de la vérification du nouveau mot de passe", + "password-updated": "Mot de passe mis a jour", + "forgot-password-generic": "Un courriel sera envoyé à cette adresse si elle existe dans notre base de données", + "not-accessible-password": "Votre serveur n'est pas accessible. Un lien pour réinitialiser votre mot de passe est dans les logs", + "not-accessible": "Votre serveur n'est pas accessible publiquement", + "email-sent": "Email envoyé", + "user-migration-needed": "Cet utilisateur doit être déplacé. Faite le se déconnecter et reconnecter afin d'entamer la procédure", + "generic-invite-email": "Erreur lors du renvoi de l'invitation par courriel", + "admin-already-exists": "Administrateur déjà existant", + "invalid-username": "Nom d'utilisateur invalide", + "critical-email-migration": "Un problème est survenu lors de la migration du courriel. Veuillez contacter le support", + "chapter-doesnt-exist": "Chapitre non existant", + "file-missing": "Fichier introuvable dans le livre", + "generic-device-delete": "Erreur lors de la suppression de l'appareil", + "send-to-kavita-email": "Envoyer à l'appareil ne peut pas être utilisé par le service e-mail de Kavita. Veuillez configurer le votre.", + "generic-favicon": "Erreur lors de la récupération de la favicon pour le domaine", + "generic-library": "Erreur critique. Essayez à nouveau.", + "delete-library-while-scan": "Vous ne pouvez pas supprimer une bibliothèque lorsqu'une analyse est en cours. Veuillez attendre la fin de l'analyse ou redémarrez Kavita, puis essayez de la supprimer", + "collection-updated": "Collection mise à jour avec succès", + "generic-error": "Erreur, essayez à nouveau", + "collection-doesnt-exist": "Collection non existante", + "device-doesnt-exist": "Cet appareil n'existe pas", + "generic-device-create": "Erreur lors de la création de l'appareil", + "generic-device-update": "Erreur lors de la mise à jour de l'appareil", + "greater-0": "{0} doit être plus grand que 0", + "send-to-device-status": "Transfert de fichier vers votre appareil", + "generic-send-to": "Erreur lors de l'envoi du/des fichiers vers l'appareil", + "series-doesnt-exist": "Série non existante", + "volume-doesnt-exist": "Volume non existant", + "bookmarks-empty": "Marque-pages ne peux pas être vide", + "no-cover-image": "Pas de couverture", + "bookmark-doesnt-exist": "Marque-page non existant", + "must-be-defined": "{0}doit être défini", + "invalid-filename": "Nom du fichier incorrect", + "file-doesnt-exist": "Fichier non existant", + "library-name-exists": "Le nom de la bibliothèque existe déjà. Veuillez choisir un nom unique pour le serveur.", + "no-library-access": "L'utilisateur n'as pas accès à la bibliothèque", + "user-doesnt-exist": "Utilisateur non existant", + "library-doesnt-exist": "Bibliothèque non existante", + "invalid-path": "Chemin invalide", + "generic-library-update": "Erreur critique lors de la mise à jour de la bibliothèque.", + "reading-list-position": "Impossible de mettre a jour la position", + "reading-list-updated": "Mis à Jour", + "reading-list-item-delete": "Impossible de supprimer le/les objets", + "reading-list-deleted": "Liste de lecture à été supprimé", + "generic-reading-list-delete": "Erreur lors de la suppression de la liste de lecture", + "libraries-restricted": "L'utilisateur n'a accès à aucune bibliothèque", + "generic-series-update": "Erreur lors de la mise à jour de la série", + "generic-cover-collection-save": "Impossible d'enregistrer l'image de couverture dans la collection", + "generic-cover-library-save": "Impossible d'enregistrer l'image de couverture dans la bibliothèque", + "browse-on-deck": "Parcourir Ce que vous avez commencé", + "browse-libraries": "Parcourir par Bibliothèques", + "query-required": "Vous devez fournir un paramètre de requête", + "encode-as-warning": "Impossible de convertir en PNG. Pour les couvertures, utilisez l'option Actualiser les couvertures. Les Marque-pages et favicons ne peuvent pas être encodée a nouveau.", + "stats-permission-denied": "Vous n'êtes pas autorisé à consulter les statistiques d'un autre utilisateur", + "generic-reading-list-update": "Erreur lors de la mise à jour de la liste de lecture", + "pdf-doesnt-exist": "PDF non existant alors qu'il devrait l'être", + "invalid-access": "Accès Invalide", + "no-image-for-page": "Aucune image pour la page {0}. Essayez d'actualiser pour autoriser la remise en cache.", + "perform-scan": "Veuillez effectuer un scan sur cette série ou bibliothèque et réessayez", + "generic-read-progress": "Erreur lors de la sauvegarde de la progression", + "generic-clear-bookmarks": "Impossible d'effacer les marque-pages", + "bookmark-permission": "Vous n'avez pas l'autorisation d'ajouter ou de retirer des marque-pages", + "bookmark-save": "Impossible de sauvegarder le marque-page", + "cache-file-find": "Impossible de trouver l'image en cache. Rechargez et recommencez.", + "name-required": "Nom ne peut pas être vide", + "valid-number": "Doit être un numéro de page valide", + "duplicate-bookmark": "Un double du marque-page est déjà existant", + "reading-list-permission": "Vous n'avez pas de droits sur cette liste de lecture ou la liste n'existe pas", + "generic-reading-list-create": "Erreur lors de la création de la liste de lecture", + "reading-list-doesnt-exist": "Liste de lecture non existante", + "series-restricted": "L'utilisateur n'as pas accès a cette Série", + "generic-scrobble-hold": "Une erreur est apparu lors de l'ajout du hold", + "no-series": "Impossible d'obtenir des séries pour la bibliothèque", + "no-series-collection": "Impossible d'obtenir une série pour la collection", + "generic-series-delete": "Erreur lors de la suppression de la série", + "series-updated": "Mise à jour réussie", + "update-metadata-fail": "Impossible de mettre à jour les métadonnées", + "age-restriction-not-applicable": "Aucune restriction", + "generic-relationship": "Erreur lors de la mise à jour des relations", + "job-already-running": "Travail déjà en cours", + "ip-address-invalid": "L'adresse IP '{0}' n'est pas valide", + "bookmark-dir-permissions": "Le répertoire de marque-page n'a pas les autorisations nécessaires pour que Kavita puisse l'utiliser", + "total-backups": "Le nombre total de sauvegardes doit être compris entre 1 et 30", + "total-logs": "Le nombre total de logs doit être compris entre 1 et 30", + "url-not-valid": "L'URL ne renvoie pas d'image valide ou nécessite une autorisation", + "url-required": "Vous devez fournir une URL pour utiliser", + "generic-cover-series-save": "Impossible d'enregistrer l'image de couverture dans la série", + "generic-cover-reading-list-save": "Impossible d'enregistrer l'image de couverture dans la liste de lecture", + "generic-cover-chapter-save": "Impossible d'enregistrer l'image de couverture dans le chapitre", + "access-denied": "Vous n'avez pas accès", + "reset-chapter-lock": "Impossible de réinitialiser le verrouillage de la couverture pour le chapitre", + "generic-user-delete": "Impossible de supprimer l'utilisateur", + "generic-user-pref": "Erreur lors de la sauvegarde des préférences", + "opds-disabled": "OPDS n'est pas activé sur ce serveur", + "on-deck": "Continuez votre lecture", + "recently-added": "Récemment Ajouté", + "browse-recently-added": "Parcourir Récemment Ajouté", + "reading-lists": "Liste de Lecture", + "browse-reading-lists": "Parcourir vos Liste de lecture", + "libraries": "Toutes les Bibliothèques", + "collections": "Toutes les Collections", + "browse-collections": "Parcourir par Collections", + "reading-list-restricted": "La liste de lecture n'existe pas ou vous n'y avez pas accès", + "search": "Rechercher", + "search-description": "Recherche de séries, collections ou listes de lecture", + "favicon-doesnt-exist": "Favicon non existante", + "not-authenticated": "L'utilisateur n'est pas authentifié", + "unable-to-register-k+": "Impossible d'enregistrer la licence en raison d'une erreur. Contactez le support de Kavita+", + "anilist-cred-expired": "Les informations d'identification AniList ont expiré ou n'ont pas été définies", + "scrobble-bad-payload": "Payload invalide de la part de Scrobble", + "theme-doesnt-exist": "Fichier de thème manquant ou invalide", + "bad-copy-files-for-download": "Impossible de copier les fichiers dans le répertoire temporaire de l'archive de téléchargement.", + "generic-create-temp-archive": "Erreur lors de la création de l'archive temporaire", + "series-restricted-age-restriction": "L'utilisateur n'est pas autorisé à visionner cette série en raison de restrictions d'âge", + "book-num": "Tome {0}", + "epub-malformed": "Fichier malformé ! Impossible de le lire.", + "epub-html-missing": "Impossible de trouver le code html approprié pour cette page", + "collection-tag-title-required": "Le titre de la collection ne peut pas être vide", + "reading-list-title-required": "Le titre de la liste de lecture ne peut être vide", + "collection-tag-duplicate": "Une collection portant ce nom existe déjà", + "device-duplicate": "Un appareil portant ce nom existe déjà", + "device-not-created": "Cet appareil n'existe pas encore. Veuillez d'abord le créer", + "send-to-permission": "Impossible d'envoyer des fichiers non-EPUB ou PDF à des appareils car ils ne sont pas pris en charge par Kindle", + "progress-must-exist": "La progression doit exister sur l'utilisateur", + "reading-list-name-exists": "Une liste de lecture de ce nom existe déjà", + "user-no-access-library-from-series": "L'utilisateur n'a pas accès à la bibliothèque à laquelle appartient cette série", + "volume-num": "Volume {0}", + "issue-num": "Numéro {0}{1}", + "chapter-num": "Chapitre {0}", + "want-to-read": "À Lire", + "browse-want-to-read": "Parcourir À Lire" } diff --git a/API/I18N/he.json b/API/I18N/he.json new file mode 100644 index 0000000000..fbb115271d --- /dev/null +++ b/API/I18N/he.json @@ -0,0 +1,23 @@ +{ + "confirm-email": "חובה לאמת תחילה כתובת דואר אלקטרוני", + "denied": "לא מאושר", + "bad-credentials": "שם משתמש או סיסמא לא נכונים", + "locked-out": "חשבונך ננעל לאחר מספר מקסימלי של נסיונות כניסה לא מוצלחים. אנא המתן/ני 10 דקות.", + "disabled-account": "חשבונך לא פעיל. אנא פנה למנהל המערכת.", + "validate-email": "אירעה תקלה בעת ניסיון וידוא כתובת הדואר האלקטרוני שלך: {0}", + "confirm-token-gen": "אירעה תקלה בעת ניסיון יצירת טוקן אישור", + "invalid-payload": "מטען לא חוקי", + "nothing-to-do": "אין מה לעשות", + "register-user": "אירעה שגיאה בעת רישום המשתמש", + "permission-denied": "אינך מורשה לבצע פעולה זו", + "password-required": "עליך להזין את הסיסמה הקיימת שלך כדי לשנות את חשבונך, אלא אם את/ה מנהל/ת מערכת", + "invalid-password": "סיסמא שגויה", + "invalid-token": "טוקן שגוי", + "unable-to-reset-key": "משהו השתבש, לא ניתן לאפס את המפתח", + "share-multiple-emails": "לא ניתן להשתמש באותה כתובת דואר אלקטרוני במספר חשבונות", + "generate-token": "אירעה תקלה בעת יצירת טוקן דוא״ל אימות. ראה/י לוגים", + "no-user": "משתמש לא קיים", + "username-taken": "שם משתמש תפוס", + "user-already-confirmed": "המשתמש כבר אושר", + "age-restriction-update": "אירעה תקלה בעת עדכון הגבלת גיל" +} diff --git a/API/I18N/it.json b/API/I18N/it.json index 399e908c60..e6022d83d8 100644 --- a/API/I18N/it.json +++ b/API/I18N/it.json @@ -156,5 +156,8 @@ "browse-libraries": "Sfoglia Librerie", "collections": "Tutte le Collezioni", "browse-collections": "Sfoglia per Collezioni", - "reading-list-restricted": "L'elenco di lettura non esiste o non hai accesso" + "reading-list-restricted": "L'elenco di lettura non esiste o non hai accesso", + "browse-want-to-read": "Sfoglia Vuoi leggere", + "want-to-read": "Vuoi leggere", + "collection-deleted": "Collezione cancellata" } diff --git a/API/I18N/ko.json b/API/I18N/ko.json new file mode 100644 index 0000000000..97e88bc161 --- /dev/null +++ b/API/I18N/ko.json @@ -0,0 +1,163 @@ +{ + "confirm-email": "먼저 이메일을 확인해야 합니다", + "bad-credentials": "자격 증명이 올바르지 않습니다", + "locked-out": "너무 많은 인증 시도로 인해 잠겼습니다. 10분 동안 기다려 주십시오.", + "invalid-password": "유효하지 않은 비밀번호", + "user-already-registered": "사용자는 이미 {0}로 등록되어 있습니다", + "password-updated": "비밀번호 업데이트됨", + "not-accessible-password": "서버에 액세스할 수 없습니다. 비밀번호 재설정 링크는 로그에 있습니다", + "not-accessible": "외부에서 서버에 액세스할 수 없습니다", + "chapter-doesnt-exist": "챕터가 존재하지 않습니다", + "file-missing": "책에서 파일을 찾을 수 없습니다", + "generic-error": "문제가 발생했습니다, 다시 시도하십시오", + "generic-device-delete": "장치를 삭제하는 중에 오류가 발생했습니다", + "greater-0": "{0}는 0보다 커야 합니다", + "send-to-device-status": "장치로 파일 전송", + "generic-send-to": "파일을 장치로 보내는 중 오류가 발생했습니다", + "volume-doesnt-exist": "볼륨이 존재하지 않습니다", + "generic-favicon": "도메인의 파비콘을 가져오는 중에 문제가 발생했습니다", + "no-library-access": "사용자는 이 라이브러리에 액세스할 수 없습니다", + "user-doesnt-exist": "사용자가 존재하지 않습니다", + "library-doesnt-exist": "라이브러리가 존재하지 않습니다", + "duplicate-bookmark": "중복된 북마크 항목이 이미 존재합니다", + "reading-list-position": "위치를 업데이트할 수 없습니다", + "reading-list-deleted": "읽기 목록이 삭제되었습니다", + "generic-reading-list-delete": "읽기 목록을 삭제하는 중에 문제가 발생했습니다", + "reading-list-doesnt-exist": "읽기 목록이 존재하지 않습니다", + "no-series": "라이브러리에 대한 시리즈를 가져올 수 없습니다", + "age-restriction-not-applicable": "제한 없음", + "generic-relationship": "관계를 업데이트하는 중에 문제가 발생했습니다", + "job-already-running": "이미 실행 중인 작업", + "url-required": "사용할 URL을 전달해야 합니다", + "reading-list-title-required": "읽기 목록 제목은 비워둘 수 없습니다", + "progress-must-exist": "사용자에게 진행 상황이 있어야 합니다", + "volume-num": "볼륨 {0}", + "chapter-num": "챕터 {0}", + "disabled-account": "계정이 비활성화되었습니다. 서버 관리자에게 문의하세요.", + "validate-email": "이메일을 확인하는 중에 문제가 발생했습니다: {0}", + "register-user": "사용자를 등록하는 중에 문제가 발생했습니다", + "confirm-token-gen": "확인 토큰을 생성하는 중에 문제가 발생했습니다", + "permission-denied": "이 작업을 수행할 수 없습니다", + "denied": "허용되지 않음", + "password-required": "관리자가 아닌 경우 계정을 변경하려면 기존 비밀번호를 입력해야 합니다", + "invalid-payload": "유효하지 않은 페이로드", + "nothing-to-do": "할 것이 없음", + "share-multiple-emails": "여러 계정에서 이메일을 공유할 수 없습니다", + "invalid-token": "유효하지 않은 토큰", + "unable-to-reset-key": "문제가 발생하여 키를 재설정할 수 없습니다", + "generate-token": "확인 이메일 토큰을 생성하는 중에 문제가 발생했습니다. 로그 보기", + "no-user": "사용자가 존재하지 않습니다", + "age-restriction-update": "연령 제한을 업데이트 하는 중에 오류가 발생했습니다", + "username-taken": "이미 사용중인 이름입니다", + "user-already-confirmed": "사용자가 이미 확인되었습니다", + "generic-user-update": "사용자를 업데이트 중에 예외가 발생했습니다", + "manual-setup-fail": "수동 설정을 완료할 수 없습니다. 초대를 취소하고 다시 만드십시오", + "user-already-invited": "사용자는 이미 이 이메일로 초대되었으며 아직 초대를 수락하지 않았습니다.", + "generic-invite-user": "사용자를 초대하는 중에 문제가 발생했습니다. 로그를 확인하십시오.", + "invalid-email-confirmation": "잘못된 이메일 확인", + "generic-user-email-update": "사용자의 이메일을 업데이트할 수 없습니다. 로그를 확인하십시오.", + "generic-password-update": "새 비밀번호를 확인하는 중에 예상치 못한 오류가 발생했습니다", + "email-sent": "이메일을 보냈습니다", + "admin-already-exists": "관리자가 이미 존재합니다", + "user-migration-needed": "이 사용자는 이전해야 합니다. 로그아웃하고 로그인하여 마이그레이션 흐름을 트리거하도록 합니다", + "forgot-password-generic": "이메일이 데이터베이스에 존재하는 경우 이메일이 이메일로 전송됩니다", + "generic-invite-email": "초대 이메일을 다시 보내는 중에 문제가 발생했습니다", + "invalid-username": "유효하지 않은 아이디", + "critical-email-migration": "이메일 이전 중에 문제가 발생했습니다. 연락처 지원", + "collection-updated": "컬렉션이 성공적으로 업데이트되었습니다", + "collection-doesnt-exist": "컬렉션이 존재하지 않습니다", + "generic-device-create": "장치를 생성하는 중에 오류가 발생했습니다", + "device-doesnt-exist": "장치가 존재하지 않습니다", + "generic-device-update": "장치를 업데이트 하는 중에 오류가 발생했습니다", + "send-to-kavita-email": "장치로 보내기는 Kavita의 이메일 서비스와 함께 사용할 수 없습니다. 직접 구성하십시오.", + "no-cover-image": "표지 이미지 없음", + "series-doesnt-exist": "시리즈가 존재하지 않습니다", + "bookmarks-empty": "북마크는 비워둘 수 없습니다", + "bookmark-doesnt-exist": "북마크가 존재하지 않습니다", + "must-be-defined": "{0}을(를) 정의해야 합니다", + "generic-library": "심각한 문제가 있었습니다. 다시 시도해 주세요.", + "invalid-filename": "유효하지 않은 파일 이름", + "file-doesnt-exist": "파일이 없습니다", + "library-name-exists": "라이브러리 이름이 이미 존재합니다. 서버에 고유한 이름을 선택하십시오.", + "invalid-path": "유효하지 않은 경로", + "delete-library-while-scan": "스캔이 진행 중인 동안에는 라이브러리를 삭제할 수 없습니다. 스캔이 완료될 때까지 기다리거나 Kavita를 다시 시작한 다음 삭제를 시도하십시오", + "pdf-doesnt-exist": "PDF가 있어야 할 때 존재하지 않음", + "no-image-for-page": "페이지 {0}에 해당 이미지가 없습니다. 재캐시를 허용하려면 새로고침해 보세요.", + "generic-clear-bookmarks": "북마크를 지울 수 없습니다", + "invalid-access": "유효하지 않은 액세스", + "generic-library-update": "라이브러리를 업데이트하는 중 심각한 문제가 발생했습니다.", + "bookmark-permission": "북마크/북마크해제 권한이 없습니다", + "perform-scan": "이 시리즈 또는 라이브러리에서 스캔을 수행하고 다시 시도하십시오", + "bookmark-save": "북마크를 저장할 수 없습니다", + "cache-file-find": "캐시된 이미지를 찾을 수 없습니다. 새로고침하고 다시 시도하세요.", + "name-required": "이름은 비워둘 수 없습니다", + "reading-list-permission": "이 읽기 목록에 대한 권한이 없거나 목록이 존재하지 않습니다", + "generic-read-progress": "진행 상황을 저장하는 중에 문제가 발생했습니다", + "valid-number": "유효한 페이지 번호여야 합니다", + "reading-list-updated": "업데이트됨", + "reading-list-item-delete": "항목을 삭제할 수 없습니다", + "series-restricted": "사용자는 이 시리즈에 액세스할 수 없습니다", + "generic-reading-list-update": "읽기 목록을 업데이트하는 중에 문제가 발생했습니다", + "generic-reading-list-create": "읽기 목록을 생성하는 중에 문제가 발생했습니다", + "generic-scrobble-hold": "보류를 추가하는 중에 오류가 발생했습니다", + "libraries-restricted": "사용자는 라이브러리에 액세스할 수 없습니다", + "no-series-collection": "컬렉션에 대한 시리즈를 가져올 수 없습니다", + "generic-series-delete": "시리즈를 삭제하는 중에 문제가 발생했습니다", + "series-updated": "성공적으로 업데이트됨", + "update-metadata-fail": "메타데이터를 업데이트할 수 없습니다", + "encode-as-warning": "PNG로 변환할 수 없습니다. 표지의 경우 표지 새로 고침을 사용하십시오. 북마크와 파비콘은 다시 인코딩할 수 없습니다.", + "ip-address-invalid": "IP 주소 '{0}'이(가) 잘못되었습니다", + "bookmark-dir-permissions": "북마크 디렉토리에 Kavita가 사용할 수 있는 올바른 권한이 없습니다", + "generic-series-update": "시리즈를 업데이트하는 중에 오류가 발생했습니다", + "total-backups": "총 백업은 1에서 30 사이여야 합니다", + "stats-permission-denied": "다른 사용자의 통계를 볼 권한이 없습니다", + "total-logs": "총 로그는 1에서 30 사이여야 합니다", + "url-not-valid": "URL이 유효한 이미지를 반환하지 않거나 승인이 필요합니다", + "generic-cover-series-save": "표지 이미지를 시리즈에 저장할 수 없습니다", + "generic-cover-collection-save": "컬렉션에 표지 이미지를 저장할 수 없습니다", + "generic-user-pref": "환경설정을 저장하는 중에 문제가 발생했습니다", + "generic-cover-reading-list-save": "읽기 목록에 표지 이미지를 저장할 수 없습니다", + "generic-cover-chapter-save": "표지 이미지를 챕터에 저장할 수 없습니다", + "generic-cover-library-save": "표지 이미지를 라이브러리에 저장할 수 없습니다", + "opds-disabled": "이 서버에서 OPDS를 사용할 수 없습니다", + "access-denied": "액세스 권한이 없습니다", + "reset-chapter-lock": "챕터에 대한 표지 잠금을 재설정할 수 없습니다", + "on-deck": "계속 읽기", + "browse-on-deck": "계속 읽기에서 찾아보기", + "reading-lists": "읽기 목록", + "libraries": "모든 라이브러리", + "generic-user-delete": "사용자를 삭제할 수 없습니다", + "recently-added": "최근에 추가됨", + "collections": "모든 컬렉션", + "browse-collections": "컬렉션에서 찾아보기", + "reading-list-restricted": "읽기 목록이 없거나 액세스 권한이 없습니다", + "query-required": "쿼리 매개변수를 전달해야 합니다", + "search-description": "시리즈, 컬렉션 또는 읽기 목록 검색", + "favicon-doesnt-exist": "파비콘이 존재하지 않습니다", + "not-authenticated": "사용자가 인증되지 않았습니다", + "anilist-cred-expired": "AniList 자격 증명이 만료되었거나 설정되지 않았습니다", + "scrobble-bad-payload": "스크로블 공급자의 잘못된 페이로드", + "bad-copy-files-for-download": "임시 디렉토리 아카이브 다운로드에 파일을 복사할 수 없습니다.", + "search": "검색", + "theme-doesnt-exist": "테마 파일이 없거나 유효하지 않음", + "generic-create-temp-archive": "임시 보관 파일을 만드는 중에 문제가 발생했습니다", + "epub-html-missing": "해당 페이지에 적합한 HTML을 찾을 수 없습니다", + "epub-malformed": "파일 형식이 잘못되었습니다! 읽을 수 없습니다.", + "collection-tag-title-required": "컬렉션 제목은 비워둘 수 없습니다", + "collection-tag-duplicate": "이 이름을 가진 컬렉션이 이미 존재합니다", + "device-not-created": "이 장치는 아직 존재하지 않습니다. 먼저 생성해주세요", + "device-duplicate": "이 이름을 가진 장치가 이미 존재합니다", + "send-to-permission": "Kindle에서 지원되지 않는 비 EPUB 또는 PDF를 장치로 보낼 수 없음", + "user-no-access-library-from-series": "사용자는 이 시리즈가 속한 라이브러리에 액세스할 수 없습니다", + "reading-list-name-exists": "이 이름의 읽기 목록이 이미 있습니다", + "series-restricted-age-restriction": "사용자는 연령 제한으로 인해 이 시리즈를 볼 수 없습니다", + "book-num": "책 {0}", + "issue-num": "이슈 {0}{1}", + "browse-recently-added": "최근 추가된 항목에서 찾아보기", + "browse-reading-lists": "읽기 목록에서 찾아보기", + "browse-libraries": "라이브러리에서 찾아보기", + "unable-to-register-k+": "오류로 인해 라이선스를 등록할 수 없습니다. Kavita+ 지원 문의", + "want-to-read": "읽고 싶어요", + "browse-want-to-read": "읽고 싶어요에서 찾아보기", + "collection-deleted": "컬렉션이 삭제되었습니다" +} diff --git a/API/I18N/nl.json b/API/I18N/nl.json index c3e686684b..29f71cf956 100644 --- a/API/I18N/nl.json +++ b/API/I18N/nl.json @@ -154,5 +154,7 @@ "book-num": "Boek {0}", "issue-num": "Uitgave {0}{1}", "chapter-num": "Hoofdstuk {0}", - "generic-scrobble-hold": "Er is een fout opgetreden bij het toevoegen van de bewaarplicht" + "generic-scrobble-hold": "Er is een fout opgetreden bij het toevoegen van de bewaarplicht", + "on-deck": "Aan het lezen", + "browse-on-deck": "Aan Het Lezen doorbladeren" } diff --git a/API/I18N/pl.json b/API/I18N/pl.json new file mode 100644 index 0000000000..f51991dda7 --- /dev/null +++ b/API/I18N/pl.json @@ -0,0 +1,60 @@ +{ + "bad-credentials": "Twoje dane uwierzytelniające są nieprawidłowe", + "disabled-account": "Twoje konto jest wyłączone. Skontaktuj się z administratorem serwera.", + "register-user": "Coś poszło nie tak podczas rejestracji użytkownika", + "validate-email": "Wystąpił problem z weryfikacją adresu e-mail: {0}", + "confirm-token-gen": "Wystąpił problem z wygenerowaniem tokena potwierdzenia", + "denied": "Niedozwolone", + "password-required": "Aby zmienić konto, musisz wprowadzić istniejące hasło, chyba że jesteś administratorem", + "invalid-password": "Nieprawidłowe hasło", + "unable-to-reset-key": "Coś poszło nie tak, nie można zresetować klucza", + "invalid-payload": "Nieprawidłowy payload", + "nothing-to-do": "Nie ma, nic do zrobienia", + "share-multiple-emails": "Nie można udostępniać wiadomości e-mail na wielu kontach", + "age-restriction-update": "Wystąpił błąd aktualizacji ograniczenia wiekowego", + "no-user": "Użytkownik nie istnieje", + "username-taken": "Nazwa użytkownika jest już zajęta", + "confirm-email": "Najpierw musisz potwierdzić swój adres e-mail", + "locked-out": "Zostałeś zablokowany z powodu zbyt wielu prób autoryzacji. Odczekaj 10 minut.", + "permission-denied": "Ta operacja jest niedozwolona", + "invalid-token": "Nieprawidłowy token", + "generate-token": "Wystąpił problem z generowaniem tokenu wiadomości e-mail z potwierdzeniem. Zobacz logi", + "generic-user-update": "Wystąpił wyjątek podczas aktualizacji użytkownika", + "user-already-registered": "Użytkownik jest już zarejestrowany jako {0}", + "user-already-confirmed": "Użytkownik jest już potwierdzony", + "manual-setup-fail": "Nie można ukończyć konfiguracji ręcznej. Anuluj i ponownie utwórz zaproszenie", + "user-already-invited": "Użytkownik jest już zaproszony pod tym adresem e-mail i nie zaakceptował jeszcze zaproszenia.", + "generic-invite-user": "Wystąpił problem z zaproszeniem użytkownika. Sprawdź logi.", + "generic-password-update": "Wystąpił nieoczekiwany błąd podczas potwierdzania nowego hasła", + "password-updated": "Hasło zaktualizowane", + "forgot-password-generic": "Wiadomość zostanie wysłana na adres e-mail, jeśli istnieje on w naszej bazie danych", + "not-accessible-password": "Serwer jest niedostępny. Link do zresetowania hasła znajduje się w logach", + "email-sent": "E-mail wysłany", + "user-migration-needed": "Ten użytkownik musi zostać zmigrowany. Niech się wyloguje i zaloguje, aby uruchomić migrację", + "generic-invite-email": "Wystąpił problem z ponownym wysłaniem wiadomości e-mail z zaproszeniem", + "critical-email-migration": "Wystąpił błąd podczas migracji poczty e-mail. Skontaktuj się z pomocą techniczną", + "file-missing": "Plik nie został znaleziony w książce", + "generic-error": "Coś poszło nie tak, spróbuj ponownie", + "device-doesnt-exist": "Urządzenie nie istnieje", + "generic-device-delete": "Wystąpił błąd podczas usuwania urządzenia", + "greater-0": "{0} musi być większe od 0", + "send-to-device-status": "Przesyłanie plików do urządzenia", + "generic-send-to": "Wystąpił błąd podczas wysyłania pliku(ów) do urządzenia", + "volume-doesnt-exist": "Tom nie istnieje", + "no-cover-image": "Brak okładki", + "invalid-email-confirmation": "Nieprawidłowe potwierdzenie e-mail", + "generic-user-email-update": "Nie można zaktualizować adresu e-mail użytkownika. Sprawdź logi.", + "not-accessible": "Serwer nie jest dostępny z zewnątrz", + "invalid-username": "Nieprawidłowa nazwa użytkownika", + "admin-already-exists": "Administrator już istnieje", + "chapter-doesnt-exist": "Rozdział nie istnieje", + "bookmarks-empty": "Zakładki nie mogą być puste", + "collection-updated": "Kolekcja została pomyślnie zaktualizowana", + "collection-doesnt-exist": "Kolekcja nie istnieje", + "generic-device-create": "Wystąpił błąd podczas tworzenia urządzenia", + "generic-device-update": "Wystąpił błąd podczas aktualizacji urządzenia", + "send-to-kavita-email": "Funkcja Wyślij do urządzenia nie może być używana z usługą e-mail Kavita. Należy skonfigurować własną.", + "bookmark-doesnt-exist": "Zakładka nie istnieje", + "series-doesnt-exist": "Seria nie istnieje", + "must-be-defined": "{0} musi być zdefiniowane" +} diff --git a/API/I18N/pt.json b/API/I18N/pt.json index bb6b064c9c..7387aa49ae 100644 --- a/API/I18N/pt.json +++ b/API/I18N/pt.json @@ -151,5 +151,12 @@ "browse-libraries": "Explorar por Bibliotecas", "browse-collections": "Explorar por Coleções", "invalid-payload": "Payload inválido", - "scrobble-bad-payload": "Payload inválido de Fornecedor de Scrobble" + "scrobble-bad-payload": "Payload inválido de Fornecedor de Scrobble", + "on-deck": "Continuar a Ler", + "browse-on-deck": "Explorar Continuar a Ler", + "issue-num": "Número {0}{1}", + "generic-scrobble-hold": "Ocorreu um erro ao adicionar o hold", + "bad-copy-files-for-download": "Não foi possível copiar os ficheiros para a diretoria temp para descarregar os arquivos.", + "want-to-read": "Leituras Futuras", + "browse-want-to-read": "Explorar Leituras Futuras" } diff --git a/API/I18N/pt_BR.json b/API/I18N/pt_BR.json new file mode 100644 index 0000000000..2f347710cc --- /dev/null +++ b/API/I18N/pt_BR.json @@ -0,0 +1,162 @@ +{ + "generic-error": "Alguma coisa deu errado. Por favor, tente outra vez", + "collection-doesnt-exist": "A coleção não existe", + "send-to-kavita-email": "Enviar para o dispositivo não pode ser usado com o serviço de e-mail da Kavita. Por favor, configure o seu próprio.", + "volume-doesnt-exist": "O volume não existe", + "no-cover-image": "Sem imagem de capa", + "invalid-filename": "Nome de arquivo inválido", + "file-doesnt-exist": "Arquivo não existe", + "no-library-access": "O usuário não tem acesso a esta biblioteca", + "library-doesnt-exist": "A biblioteca não existe", + "delete-library-while-scan": "Você não pode excluir uma biblioteca enquanto uma verificação estiver em andamento. Aguarde a conclusão da verificação ou reinicie o Kavita e tente excluir", + "generic-library-update": "Ocorreu um problema crítico ao atualizar a biblioteca.", + "confirm-email": "Você deve confirmar seu e-mail primeiro", + "bad-credentials": "Suas credenciais não estão corretas", + "locked-out": "Você foi bloqueado por muitas tentativas de autorização. Aguarde 10 minutos.", + "validate-email": "Ocorreu um problema ao validar seu e-mail: {0}", + "denied": "Não permitido", + "invalid-password": "Senha Inválida", + "invalid-token": "Token inválido", + "unable-to-reset-key": "Algo deu errado, não foi possível redefinir a chave", + "nothing-to-do": "Nada para fazer", + "share-multiple-emails": "Você não pode compartilhar e-mails em várias contas", + "age-restriction-update": "Ocorreu um erro ao atualizar a restrição de idade", + "no-user": "Usuário não existe", + "username-taken": "Nome de usuário já em uso", + "user-already-confirmed": "O usuário já está confirmado", + "manual-setup-fail": "A configuração manual não pode ser concluída. Cancele e recrie o convite", + "user-already-registered": "O usuário já está registrado como {0}", + "generic-invite-user": "Ocorreu um problema ao convidar o usuário. Verifique os registros.", + "invalid-email-confirmation": "Confirmação de e-mail inválido", + "password-updated": "Senha atualizada", + "forgot-password-generic": "Um e-mail será enviado para o e-mail caso exista em nosso banco de dados", + "not-accessible-password": "Seu servidor não está acessível. O link para redefinir sua senha está nos registros", + "email-sent": "E-mail enviado", + "generic-invite-email": "Ocorreu um problema ao reenviar o e-mail de convite", + "admin-already-exists": "O administrador já existe", + "invalid-username": "Nome de usuário Inválido", + "critical-email-migration": "Ocorreu um problema durante a migração de e-mail. Entre em contato com o suporte", + "chapter-doesnt-exist": "Capítulo não existe", + "collection-updated": "Coleção atualizada com sucesso", + "device-doesnt-exist": "O dispositivo não existe", + "generic-device-create": "Ocorreu um erro ao criar o dispositivo", + "generic-device-update": "Ocorreu um erro ao atualizar o dispositivo", + "generic-device-delete": "Ocorreu um erro ao excluir o dispositivo", + "greater-0": "{0} deve ser maior que 0", + "send-to-device-status": "Transferindo arquivos para o seu dispositivo", + "generic-send-to": "Ocorreu um erro ao enviar o(s) arquivo(s) para o dispositivo", + "series-doesnt-exist": "A série não existe", + "bookmarks-empty": "Os marcadores não podem estar vazios", + "bookmark-doesnt-exist": "Favorito não existe", + "must-be-defined": "{0} deve ser definido", + "generic-favicon": "Ocorreu um problema ao buscar favicon para o domínio", + "library-name-exists": "O nome da biblioteca já existe. Escolha um nome exclusivo para o servidor.", + "generic-library": "Houve um problema crítico. Por favor, tente novamente.", + "user-doesnt-exist": "Usuário não existe", + "invalid-path": "Caminho inválido", + "pdf-doesnt-exist": "PDF não existe quando deveria", + "invalid-access": "Acesso inválido", + "no-image-for-page": "Essa imagem não existe para a página {0}. Tente atualizar para permitir o re-cache.", + "bookmark-permission": "Você não tem permissão para marcar/desmarcar", + "bookmark-save": "Não foi possível salvar o favorito", + "cache-file-find": "Não foi possível encontrar a imagem em cache. Recarregue e tente novamente.", + "name-required": "O nome não pode estar vazio", + "valid-number": "Deve ser um número de página válido", + "duplicate-bookmark": "Já existe uma entrada de marcador duplicada", + "reading-list-position": "Não foi possível atualizar a posição", + "reading-list-updated": "Atualizado", + "reading-list-deleted": "A lista de leitura foi excluída", + "generic-reading-list-update": "Ocorreu um problema ao atualizar a lista de leitura", + "generic-reading-list-create": "Ocorreu um problema ao criar a lista de leitura", + "reading-list-doesnt-exist": "A lista de leitura não existe", + "series-restricted": "O usuário não tem acesso a esta série", + "libraries-restricted": "O usuário não tem acesso a nenhuma biblioteca", + "no-series": "Não foi possível obter a série para a Biblioteca", + "generic-series-delete": "Ocorreu um problema ao excluir a série", + "series-updated": "Atualizado com sucesso", + "update-metadata-fail": "Não foi possível atualizar os metadados", + "age-restriction-not-applicable": "Sem Restrição", + "job-already-running": "Tarefa já em execução", + "encode-as-warning": "Você não pode converter para PNG. Para capas, use Atualizar capas. Marcadores e favicons não podem ser codificados de volta.", + "ip-address-invalid": "O endereço IP '{0}' é inválido", + "total-logs": "O total de registros deve estar entre 1 e 30", + "stats-permission-denied": "Você não está autorizado a visualizar as estatísticas de outro usuário", + "url-not-valid": "URL não retorna uma imagem válida ou requer autorização", + "generic-cover-collection-save": "Não foi possível salvar a imagem da capa na Coleção", + "generic-cover-reading-list-save": "Não é possível salvar a imagem da capa na lista de leitura", + "generic-cover-chapter-save": "Não foi possível salvar a imagem da capa no Capítulo", + "access-denied": "Você não tem acesso", + "reset-chapter-lock": "Não é possível redefinir o bloqueio da tampa para o Capítulo", + "generic-user-delete": "Não foi possível excluir o usuário", + "on-deck": "Na Estante", + "browse-on-deck": "Navegar Na Estante", + "recently-added": "Adicionado Recentemente", + "browse-recently-added": "Navegar no Adicionado Recentemente", + "reading-lists": "Listas de leitura", + "search-description": "Pesquisar por séries, coleções ou listas de leitura", + "favicon-doesnt-exist": "O favicon não existe", + "device-duplicate": "Já existe um dispositivo com este nome", + "disabled-account": "Sua conta está desativada. Entre em contato com o administrador do servidor.", + "register-user": "Algo deu errado ao registrar o usuário", + "confirm-token-gen": "Ocorreu um problema ao gerar um token de confirmação", + "permission-denied": "Você não tem permissão para esta operação", + "password-required": "Você deve inserir sua senha existente para alterar sua conta, a menos que seja um administrador", + "generate-token": "Ocorreu um problema ao gerar um token de e-mail de confirmação. Ver registros", + "generic-user-update": "Houve uma exceção ao atualizar o usuário", + "user-already-invited": "O usuário já foi convidado neste e-mail e ainda não aceitou o convite.", + "generic-user-email-update": "Não foi possível atualizar o e-mail do usuário. Verifique os registros.", + "generic-password-update": "Ocorreu um erro inesperado ao confirmar a nova senha", + "not-accessible": "Seu servidor não está acessível externamente", + "user-migration-needed": "Este usuário precisa migrar. Faça com que eles saiam e façam login para acionar um fluxo de migração", + "file-missing": "O arquivo não foi encontrado no livro", + "generic-reading-list-delete": "Ocorreu um problema ao excluir a lista de leitura", + "generic-scrobble-hold": "Ocorreu um erro ao adicionar a retenção", + "no-series-collection": "Não foi possível obter a série para a Coleção", + "generic-series-update": "Ocorreu um erro ao atualizar a série", + "generic-relationship": "Ocorreu um problema ao atualizar as relações", + "bookmark-dir-permissions": "O Diretório de marcadores não tem as permissões corretas para Kavita usar", + "total-backups": "O total de backups deve estar entre 1 e 30", + "url-required": "Você deve passar uma url para usar", + "generic-cover-series-save": "Não é possível salvar a imagem da capa na Séries", + "generic-cover-library-save": "Não foi possível salvar a imagem da capa na Biblioteca", + "generic-user-pref": "Ocorreu um problema ao salvar as preferências", + "opds-disabled": "OPDS não está ativado neste servidor", + "browse-reading-lists": "Navegar por Listas de Leitura", + "libraries": "Todas as Bibliotecas", + "browse-libraries": "Navegar nas Bibliotecas", + "collections": "Todas as Coleções", + "browse-collections": "Navegar nas Coleções", + "reading-list-restricted": "A lista de leitura não existe ou você não tem acesso", + "query-required": "Você deve passar um parâmetro de consulta", + "search": "Pesquisar", + "not-authenticated": "O usuário não está autenticado", + "unable-to-register-k+": "Não foi possível registrar a licença devido a um erro. Entre em contato com o Suporte Kavita+", + "anilist-cred-expired": "As credenciais do AniList expiraram ou não foram definidas", + "scrobble-bad-payload": "Carga útil inválida do provedor Scrobble", + "theme-doesnt-exist": "Arquivo de tema ausente ou inválido", + "bad-copy-files-for-download": "Não é possível copiar os arquivos para o download do arquivo do diretório temporário.", + "generic-create-temp-archive": "Ocorreu um problema ao criar o arquivo temporário", + "epub-malformed": "O arquivo está malformado! Não posso ler.", + "epub-html-missing": "Não foi possível encontrar o html apropriado para essa página", + "collection-tag-title-required": "O título da coleção não pode estar vazio", + "reading-list-title-required": "O título da lista de leitura não pode estar vazio", + "collection-tag-duplicate": "Já existe uma coleção com este nome", + "device-not-created": "Este dispositivo ainda não existe. Por favor, crie primeiro", + "send-to-permission": "Não é possível enviar arquivos não EPUB ou PDF para dispositivos porque não são compatíveis com o Kindle", + "progress-must-exist": "O progresso deve existir no usuário", + "reading-list-name-exists": "Já existe uma lista de leitura com este nome", + "user-no-access-library-from-series": "O usuário não tem acesso à biblioteca a que esta série pertence", + "series-restricted-age-restriction": "O usuário não tem permissão para ver esta série devido a restrições de idade", + "volume-num": "Volume {0}", + "book-num": "Livro {0}", + "perform-scan": "Realize uma varredura nesta série ou biblioteca e tente novamente", + "generic-read-progress": "Ocorreu um problema ao salvar o progresso", + "generic-clear-bookmarks": "Não foi possível limpar os marcadores", + "reading-list-permission": "Você não tem permissões nesta lista de leitura ou a lista não existe", + "reading-list-item-delete": "Não foi possível excluir os itens", + "invalid-payload": "Carga inválida", + "issue-num": "Número {0}{1}", + "chapter-num": "Capítulo {0}", + "want-to-read": "Quero Ler", + "browse-want-to-read": "Navegar no Quero Ler" +} diff --git a/API/I18N/th.json b/API/I18N/th.json index 95f754887a..9750001dab 100644 --- a/API/I18N/th.json +++ b/API/I18N/th.json @@ -156,5 +156,7 @@ "query-required": "คุณต้องส่งพารามิเตอร์การค้นหา", "theme-doesnt-exist": "ไฟล์ธีมหายไปหรือไม่ถูกต้อง", "epub-html-missing": "ไม่พบ html ที่เหมาะสมสำหรับหน้านั้น", - "collection-tag-title-required": "ชื่อคอลเลกชันต้องไม่ว่างเปล่า" + "collection-tag-title-required": "ชื่อคอลเลกชันต้องไม่ว่างเปล่า", + "want-to-read": "ต้องการอ่าน", + "browse-want-to-read": "ดูรายการต้องการอ่าน" } diff --git a/API/I18N/zh_Hans.json b/API/I18N/zh_Hans.json index cdf51d5427..8dcdce2572 100644 --- a/API/I18N/zh_Hans.json +++ b/API/I18N/zh_Hans.json @@ -1,21 +1,163 @@ { - "bad-credentials": "你的用户信息不匹配", + "bad-credentials": "您的登录信息不正确", "validate-email": "验证你的邮件时出了点问题: {0}", "confirm-token-gen": "生成认证令牌时出现问题", "denied": "未被允许", "password-required": "除非您是管理员,否则您必须输入现有密码才能更改帐户信息", "invalid-token": "无效令牌", "unable-to-reset-key": "出错了,无法重置", - "confirm-email": "你必须先确认你的邮箱", - "disabled-account": "你的账号已被关闭。联系服务器的管理员。", + "confirm-email": "您必须先确认电子邮件", + "disabled-account": "您的账号已被禁用,请联系管理员。", "register-user": "注册用户时出现一些错误", - "locked-out": "您因多次错误登陆已被阻止。请稍等 10 分钟。", + "locked-out": "您因多次错误登陆已被阻止,请稍等 10 分钟", "permission-denied": "您无权执行此操作", "invalid-password": "无效密码", - "generate-token": "生成确认电子邮件令牌时出现问题。参见日志", + "generate-token": "生成确认电子邮件令牌时出现问题,参见日志", "generic-user-update": "更新用户时出现了异常", - "share-multiple-emails": "不能在多个账户间共享电子邮件", - "age-restriction-update": "年龄限制更新出错", + "share-multiple-emails": "无法在多个账户间共用电子邮件地址", + "age-restriction-update": "更新年龄限制时出错", "no-user": "用户不存在", - "username-taken": "用户名已被使用" + "username-taken": "用户名已被使用", + "generic-invite-user": "邀请用户时出现问题,请检查日志。", + "invalid-email-confirmation": "邮件确认信息无效", + "password-updated": "密码已更新", + "forgot-password-generic": "如果电子邮件地址存在于数据库中,则会向该地址发送电子邮件", + "admin-already-exists": "管理员已存在", + "file-missing": "在书籍中没有找到相关文件", + "collection-updated": "收藏更新成功", + "device-doesnt-exist": "设备不存在", + "volume-doesnt-exist": "卷不存在", + "generic-password-update": "确认新密码时出现意外错误", + "not-accessible": "此服务器无法从外部访问", + "email-sent": "电子邮件已发送", + "generic-invite-email": "重新发送邀请电子邮件时出现问题", + "invalid-username": "用户名无效", + "generic-error": "发生了一些小问题,请重新尝试", + "collection-doesnt-exist": "收藏不存在", + "generic-device-create": "创建设备时出错", + "generic-device-update": "更新设备时出错", + "generic-device-delete": "删除设备时出错", + "greater-0": "{0}必须大于 0", + "send-to-device-status": "正在向您的设备传输文件", + "generic-send-to": "将文件发送到设备时出错", + "series-doesnt-exist": "系列不存在", + "bookmarks-empty": "书签不能为空", + "no-cover-image": "无封面", + "bookmark-doesnt-exist": "书签不存在", + "must-be-defined": "必须定义{0}", + "invalid-filename": "无效的文件名", + "file-doesnt-exist": "文件不存在", + "library-name-exists": "资料库名称已存在,请重新指定一个唯一的名称。", + "generic-library": "发生了一个严重错误,请重试。", + "delete-library-while-scan": "在扫描过程中,您无法删除资料库。请等待扫描完成或重启启动Kavita,然后尝试删除", + "generic-library-update": "更新资料库时产生一个严重问题。", + "no-image-for-page": "第 {0} 页没有此图像,请尝试刷新重新缓存。", + "perform-scan": "请对此系列或者资料库执行扫描,并且重新尝试", + "generic-cover-chapter-save": "无法为该章节保存封面", + "generic-cover-library-save": "无法为该资料库保存封面", + "access-denied": "无权访问", + "generic-clear-bookmarks": "无法清除书签", + "opds-disabled": "此服务器未启用OPDS", + "bookmark-permission": "您无权加入书签或删除书签", + "bookmark-save": "无法保存书签", + "reading-list-permission": "您无权访问阅读清单,或者阅读清单不存在", + "reading-list-updated": "已更新", + "reading-list-item-delete": "无法删除条目", + "reading-list-deleted": "已删除阅读清单", + "no-library-access": "用户无权访问此资料库", + "user-doesnt-exist": "用户不存在", + "library-doesnt-exist": "资料库不存在", + "invalid-path": "无效路径", + "invalid-access": "无效访问", + "generic-read-progress": "保存进度时出现问题", + "cache-file-find": "找不到缓存的图像,请重新加载并重试。", + "name-required": "名称不能为空", + "valid-number": "必须是有效的页码", + "duplicate-bookmark": "相同的书签已存在", + "reading-list-position": "无法更新定位", + "generic-reading-list-delete": "删除阅读清单时出现问题", + "generic-reading-list-update": "更新阅读清单时出现问题", + "generic-reading-list-create": "建立阅读清单时出现问题", + "reading-list-doesnt-exist": "阅读清单不存在", + "series-restricted": "用户无权访问此系列", + "libraries-restricted": "用户无权访问任何资料库", + "generic-series-delete": "删除系列时出现问题", + "generic-series-update": "更新系列时出错", + "series-updated": "更新成功", + "update-metadata-fail": "无法更新元数据", + "age-restriction-not-applicable": "无限制", + "job-already-running": "任务运行中", + "total-backups": "备份总数必须介于1到30之间", + "ip-address-invalid": "IP地址“{0}”无效", + "total-logs": "日志总数必须介于1到30之间", + "stats-permission-denied": "您无权查看其他用户的统计信息", + "url-not-valid": "URL无法返回有效图像或者需要授权", + "generic-cover-series-save": "无法为该系列保存封面", + "generic-cover-collection-save": "无法为该收藏保存封面", + "generic-cover-reading-list-save": "无法为该阅读列表保存封面", + "generic-user-delete": "无法删除用户", + "generic-user-pref": "保存首选项时出现问题", + "browse-reading-lists": "按阅读清单浏览", + "libraries": "所有资料库", + "browse-libraries": "按资料库浏览", + "collections": "所有收藏", + "browse-collections": "按收藏浏览", + "reading-list-restricted": "阅读清单不存在或您没有访问权限", + "search-description": "搜索系列、收藏或阅读清单", + "favicon-doesnt-exist": "图标不存在", + "not-authenticated": "用户未通过身份验证", + "anilist-cred-expired": "AniList凭据已过期或未设置", + "theme-doesnt-exist": "主题文件丢失或者无效", + "generic-user-email-update": "无法更新用户的电子邮件。请检查日志。", + "epub-malformed": "文件格式不正确!无法读取。", + "user-already-invited": "已经向此用户的电子邮箱发送了邀请邮件,但该用户尚未接受邀请。", + "want-to-read": "想读", + "browse-want-to-read": "浏览想读", + "epub-html-missing": "找不到该页面相应的html文件", + "collection-tag-title-required": "收藏标题不能为空", + "reading-list-title-required": "阅读清单标题不能为空", + "collection-tag-duplicate": "收藏的名称已存在", + "chapter-num": "第{0}话", + "not-accessible-password": "您的服务器无法访问,重置密码的链接位于日志中", + "invalid-payload": "无效的数据", + "nothing-to-do": "没有需要处理的任务", + "user-already-confirmed": "用户已确认", + "manual-setup-fail": "无法进行手动设置,请取消邀请并重新创建", + "user-already-registered": "用户已经注册为{0}", + "critical-email-migration": "更改电子邮件地址时出现问题,请联系支持人员", + "chapter-doesnt-exist": "章节不存在", + "send-to-kavita-email": "无法使用Kavita服务向设备发送电子邮件,请自行配置。", + "generic-favicon": "获取图标时出现问题", + "pdf-doesnt-exist": "PDF文件应该存在,但未找到", + "no-series": "无法获取资料库中的系列", + "no-series-collection": "无法获取收藏中的系列", + "bookmark-dir-permissions": "书签目录权限不正确", + "on-deck": "最近阅读", + "browse-on-deck": "浏览最近阅读", + "recently-added": "最近加入", + "browse-recently-added": "浏览最近加入", + "reading-lists": "阅读清单", + "search": "搜索", + "unable-to-register-k+": "因为一些错误导致无法注册许可证。请联系 Kavita+ 支持人员", + "device-duplicate": "设备名称已存在", + "device-not-created": "设备不存在,请先创建", + "send-to-permission": "无法向设备发送Kindel不支持的非EPUB格式或者PDF格式文件", + "reading-list-name-exists": "此名称的阅读清单已存在", + "volume-num": "第{0}卷", + "issue-num": "问题编号{0}{1}", + "book-num": "第{0}本", + "user-migration-needed": "该用户需要进行迁移。通知他们注销并重新登录,以触发迁移流程", + "generic-relationship": "更新关系时发生了问题", + "encode-as-warning": "无法转换为PNG格式。对于封面,请使用刷新封面功能。书签和网站图标无法再进行编码。", + "url-required": "必须提供一个URL才能使用", + "series-restricted-age-restriction": "由于年龄限制用户无权查看此系列", + "user-no-access-library-from-series": "用户无法访问此系列所属的资料库", + "generic-create-temp-archive": "创建临时档案时出现问题", + "query-required": "您必须传递一个查询参数", + "scrobble-bad-payload": "Scrobble服务提供商的数据无效", + "bad-copy-files-for-download": "无法复制文件至临时下载目录", + "progress-must-exist": "用户进程必须存在", + "generic-scrobble-hold": "启用锁定时发生错误", + "reset-chapter-lock": "无法重置章节的封面锁", + "collection-deleted": "收藏已删除" } diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs index a4a9d3ccb4..fd4349c909 100644 --- a/API/Services/ArchiveService.cs +++ b/API/Services/ArchiveService.cs @@ -22,7 +22,7 @@ public interface IArchiveService { void ExtractArchive(string archivePath, string extractPath); int GetNumberOfPagesFromArchive(string archivePath); - string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format); + string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format, CoverImageSize size = CoverImageSize.Default); bool IsValidArchive(string archivePath); ComicInfo? GetComicInfo(string archivePath); ArchiveLibrary CanOpen(string archivePath); @@ -205,7 +205,7 @@ public int GetNumberOfPagesFromArchive(string archivePath) /// Where to output the file, defaults to covers directory /// When saving the file, use encoding /// - public string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format) + public string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format, CoverImageSize size = CoverImageSize.Default) { if (archivePath == null || !IsValidArchive(archivePath)) return string.Empty; try @@ -221,7 +221,7 @@ public string GetCoverImage(string archivePath, string fileName, string outputDi var entry = archive.Entries.Single(e => e.FullName == entryName); using var stream = entry.Open(); - return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format, size); } case ArchiveLibrary.SharpCompress: { @@ -232,7 +232,7 @@ public string GetCoverImage(string archivePath, string fileName, string outputDi var entry = archive.Entries.Single(e => e.Key == entryName); using var stream = entry.OpenEntryStream(); - return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format, size); } case ArchiveLibrary.NotSupported: _logger.LogWarning("[GetCoverImage] This archive cannot be read: {ArchivePath}. Defaulting to no cover image", archivePath); @@ -246,7 +246,7 @@ public string GetCoverImage(string archivePath, string fileName, string outputDi { _logger.LogWarning(ex, "[GetCoverImage] There was an exception when reading archive stream: {ArchivePath}. Defaulting to no cover image", archivePath); _mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService, - "This archive cannot be read or not supported", ex); + "This archive cannot be read or not supported", ex); // TODO: Localize this } return string.Empty; diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 54d2257ab2..06424c1a37 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -33,7 +33,7 @@ namespace API.Services; public interface IBookService { int GetNumberOfPages(string filePath); - string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat); + string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default); ComicInfo? GetComicInfo(string filePath); ParserInfo? ParseInfo(string filePath); /// @@ -1196,13 +1196,13 @@ private static void CreateToCChapter(EpubBookRef book, EpubNavigationItemRef nav /// Where to output the file, defaults to covers directory /// When saving the file, use encoding /// - public string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat) + public string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default) { if (!IsValidFile(fileFilePath)) return string.Empty; if (Parser.IsPdf(fileFilePath)) { - return GetPdfCoverImage(fileFilePath, fileName, outputDirectory, encodeFormat); + return GetPdfCoverImage(fileFilePath, fileName, outputDirectory, encodeFormat, size); } using var epubBook = EpubReader.OpenBook(fileFilePath, BookReaderOptions); @@ -1217,20 +1217,20 @@ public string GetCoverImage(string fileFilePath, string fileName, string outputD if (coverImageContent == null) return string.Empty; using var stream = coverImageContent.GetContentStream(); - return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat, size); } catch (Exception ex) { _logger.LogWarning(ex, "[BookService] There was a critical error and prevented thumbnail generation on {BookFile}. Defaulting to no cover image", fileFilePath); _mediaErrorService.ReportMediaIssue(fileFilePath, MediaErrorProducer.BookService, - "There was a critical error and prevented thumbnail generation", ex); + "There was a critical error and prevented thumbnail generation", ex); // TODO: Localize this } return string.Empty; } - private string GetPdfCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat) + private string GetPdfCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size) { try { @@ -1240,7 +1240,7 @@ private string GetPdfCoverImage(string fileFilePath, string fileName, string out using var stream = StreamManager.GetStream("BookService.GetPdfPage"); GetPdfPage(docReader, 0, stream); - return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat, size); } catch (Exception ex) diff --git a/API/Services/CollectionTagService.cs b/API/Services/CollectionTagService.cs index 7c5aeaa718..b024d687a6 100644 --- a/API/Services/CollectionTagService.cs +++ b/API/Services/CollectionTagService.cs @@ -17,6 +17,7 @@ namespace API.Services; public interface ICollectionTagService { Task TagExistsByName(string name); + Task DeleteTag(CollectionTag tag); Task UpdateTag(CollectionTagDto dto); Task AddTagToSeries(CollectionTag? tag, IEnumerable seriesIds); Task RemoveTagFromSeries(CollectionTag? tag, IEnumerable seriesIds); @@ -49,6 +50,12 @@ public async Task TagExistsByName(string name) return await _unitOfWork.CollectionTagRepository.TagExists(name); } + public async Task DeleteTag(CollectionTag tag) + { + _unitOfWork.CollectionTagRepository.Remove(tag); + return await _unitOfWork.CommitAsync(); + } + public async Task UpdateTag(CollectionTagDto dto) { var existingTag = await _unitOfWork.CollectionTagRepository.GetTagAsync(dto.Id); @@ -130,6 +137,7 @@ public void AddTagToSeriesMetadata(CollectionTag? tag, SeriesMetadata metadata) public async Task RemoveTagFromSeries(CollectionTag? tag, IEnumerable seriesIds) { if (tag == null) return false; + tag.SeriesMetadatas ??= new List(); foreach (var seriesIdToRemove in seriesIds) { tag.SeriesMetadatas.Remove(tag.SeriesMetadatas.Single(sm => sm.SeriesId == seriesIdToRemove)); diff --git a/API/Services/ImageService.cs b/API/Services/ImageService.cs index 54ea5ec38c..05f0a4434d 100644 --- a/API/Services/ImageService.cs +++ b/API/Services/ImageService.cs @@ -21,7 +21,7 @@ namespace API.Services; public interface IImageService { void ExtractImages(string fileFilePath, string targetDirectory, int fileCount = 1); - string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat); + string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size); /// /// Creates a Thumbnail version of a base64 image @@ -40,7 +40,7 @@ public interface IImageService /// /// /// - string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat); + string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default); /// /// Writes out a thumbnail by file path input /// @@ -49,7 +49,7 @@ public interface IImageService /// /// /// - string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat); + string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default); /// /// Converts the passed image to encoding and outputs it in the same directory /// @@ -87,6 +87,7 @@ public class ImageService : IImageService /// public const int LibraryThumbnailWidth = 32; + private static readonly string[] ValidIconRelations = { "icon", "apple-touch-icon", @@ -124,13 +125,14 @@ public void ExtractImages(string? fileFilePath, string targetDirectory, int file } } - public string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat) + public string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size) { if (string.IsNullOrEmpty(path)) return string.Empty; try { - using var thumbnail = Image.Thumbnail(path, ThumbnailWidth, height: ThumbnailHeight, size: Enums.Size.Force); + var dims = size.GetDimensions(); + using var thumbnail = Image.Thumbnail(path, dims.Width, height: dims.Height, size: Enums.Size.Force); var filename = fileName + encodeFormat.GetExtension(); thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename)); return filename; @@ -152,9 +154,10 @@ public string GetCoverImage(string path, string fileName, string outputDirectory /// Where to output the file, defaults to covers directory /// Export the file as the passed encoding /// File name with extension of the file. This will always write to - public string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat) + public string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default) { - using var thumbnail = Image.ThumbnailStream(stream, ThumbnailWidth, height: ThumbnailHeight, size: Enums.Size.Force); + var dims = size.GetDimensions(); + using var thumbnail = Image.ThumbnailStream(stream, dims.Width, height: dims.Height, size: Enums.Size.Force); var filename = fileName + encodeFormat.GetExtension(); _directoryService.ExistOrCreate(outputDirectory); try @@ -165,9 +168,10 @@ public string WriteCoverThumbnail(Stream stream, string fileName, string outputD return filename; } - public string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat) + public string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default) { - using var thumbnail = Image.Thumbnail(sourceFile, ThumbnailWidth, height: ThumbnailHeight, size: Enums.Size.Force); + var dims = size.GetDimensions(); + using var thumbnail = Image.Thumbnail(sourceFile, dims.Width, height: dims.Height, size: Enums.Size.Force); var filename = fileName + encodeFormat.GetExtension(); _directoryService.ExistOrCreate(outputDirectory); try @@ -416,31 +420,62 @@ public static string GetThumbnailFormat(int chapterId) public static string GetWebLinkFormat(string url, EncodeFormat encodeFormat) { - return $"{new Uri(url).Host}{encodeFormat.GetExtension()}"; + return $"{new Uri(url).Host.Replace("www.", string.Empty)}{encodeFormat.GetExtension()}"; } - public static string CreateMergedImage(IList coverImages, string dest) + public static void CreateMergedImage(IList coverImages, CoverImageSize size, string dest) { - var image = Image.Black(ThumbnailWidth, ThumbnailHeight); // 320x455 + var dims = size.GetDimensions(); + int rows, cols; - var thumbnailWidth = image.Width / 2; - var thumbnailHeight = image.Height / 2; + if (coverImages.Count == 1) + { + rows = 1; + cols = 1; + } + else if (coverImages.Count == 2) + { + rows = 1; + cols = 2; + } + else if (coverImages.Count == 3) + { + rows = 2; + cols = 2; + } + else + { + // Default to 2x2 layout for more than 3 images + rows = 2; + cols = 2; + } + + var image = Image.Black(dims.Width, dims.Height); + + var thumbnailWidth = image.Width / cols; + var thumbnailHeight = image.Height / rows; for (var i = 0; i < coverImages.Count; i++) { var tile = Image.NewFromFile(coverImages[i], access: Enums.Access.Sequential); - - // Resize the tile to fit the thumbnail size tile = tile.ThumbnailImage(thumbnailWidth, height: thumbnailHeight); - var x = (i % 2) * thumbnailWidth; - var y = (i / 2) * thumbnailHeight; + var row = i / cols; + var col = i % cols; + + var x = col * thumbnailWidth; + var y = row * thumbnailHeight; + + if (coverImages.Count == 3 && i == 2) + { + x = (image.Width - thumbnailWidth) / 2; + y = thumbnailHeight; + } image = image.Insert(tile, x, y); } image.WriteToFile(dest); - return dest; } } diff --git a/API/Services/MediaErrorService.cs b/API/Services/MediaErrorService.cs index 6615dab7af..30c51e61d1 100644 --- a/API/Services/MediaErrorService.cs +++ b/API/Services/MediaErrorService.cs @@ -37,6 +37,7 @@ public async Task ReportMediaIssueAsync(string filename, MediaErrorProducer prod public void ReportMediaIssue(string filename, MediaErrorProducer producer, string errorMessage, Exception ex) { + // TODO: Localize all these messages // To avoid overhead on commits, do async. We don't need to wait. BackgroundJob.Enqueue(() => ReportMediaIssueAsync(filename, producer, errorMessage, ex.Message)); } diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs index ff5a18df2a..f6fb060633 100644 --- a/API/Services/MetadataService.cs +++ b/API/Services/MetadataService.cs @@ -33,7 +33,7 @@ public interface IMetadataService /// Overrides any cache logic and forces execution Task GenerateCoversForSeries(int libraryId, int seriesId, bool forceUpdate = true); - Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, bool forceUpdate = false); + Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceUpdate = false); Task RemoveAbandonedMetadataKeys(); } @@ -65,7 +65,7 @@ public MetadataService(IUnitOfWork unitOfWork, ILogger logger, /// /// Force updating cover image even if underlying file has not been modified or chapter already has a cover image /// Convert image to Encoding Format when extracting the cover - private Task UpdateChapterCoverImage(Chapter chapter, bool forceUpdate, EncodeFormat encodeFormat) + private Task UpdateChapterCoverImage(Chapter chapter, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize) { var firstFile = chapter.Files.MinBy(x => x.Chapter); if (firstFile == null) return Task.FromResult(false); @@ -79,7 +79,7 @@ private Task UpdateChapterCoverImage(Chapter chapter, bool forceUpdate, En _logger.LogDebug("[MetadataService] Generating cover image for {File}", firstFile.FilePath); chapter.CoverImage = _readingItemService.GetCoverImage(firstFile.FilePath, - ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId), firstFile.Format, encodeFormat); + ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId), firstFile.Format, encodeFormat, coverImageSize); _unitOfWork.ChapterRepository.Update(chapter); _updateEvents.Add(MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter)); return Task.FromResult(true); @@ -143,7 +143,7 @@ private Task UpdateSeriesCoverImage(Series? series, bool forceUpdate) /// /// /// - private async Task ProcessSeriesCoverGen(Series series, bool forceUpdate, EncodeFormat encodeFormat) + private async Task ProcessSeriesCoverGen(Series series, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize) { _logger.LogDebug("[MetadataService] Processing cover image generation for series: {SeriesName}", series.OriginalName); try @@ -156,7 +156,7 @@ private async Task ProcessSeriesCoverGen(Series series, bool forceUpdate, Encode var index = 0; foreach (var chapter in volume.Chapters) { - var chapterUpdated = await UpdateChapterCoverImage(chapter, forceUpdate, encodeFormat); + var chapterUpdated = await UpdateChapterCoverImage(chapter, forceUpdate, encodeFormat, coverImageSize); // If cover was update, either the file has changed or first scan and we should force a metadata update UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated); if (index == 0 && chapterUpdated) @@ -208,7 +208,9 @@ public async Task GenerateCoversForLibrary(int libraryId, bool forceUpdate = fal await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.CoverUpdateProgressEvent(library.Id, 0F, ProgressEventType.Started, $"Starting {library.Name}")); - var encodeFormat = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs; + var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); + var encodeFormat = settings.EncodeMediaAs; + var coverImageSize = settings.CoverImageSize; for (var chunk = 1; chunk <= chunkInfo.TotalChunks; chunk++) { @@ -238,7 +240,7 @@ await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, try { - await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat); + await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize); } catch (Exception ex) { @@ -288,8 +290,10 @@ public async Task GenerateCoversForSeries(int libraryId, int seriesId, bool forc return; } - var encodeFormat = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs; - await GenerateCoversForSeries(series, encodeFormat, forceUpdate); + var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); + var encodeFormat = settings.EncodeMediaAs; + var coverImageSize = settings.CoverImageSize; + await GenerateCoversForSeries(series, encodeFormat, coverImageSize, forceUpdate); } /// @@ -298,13 +302,13 @@ public async Task GenerateCoversForSeries(int libraryId, int seriesId, bool forc /// A full Series, with metadata, chapters, etc /// When saving the file, what encoding should be used /// - public async Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, bool forceUpdate = false) + public async Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceUpdate = false) { var sw = Stopwatch.StartNew(); await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.CoverUpdateProgressEvent(series.LibraryId, 0F, ProgressEventType.Started, series.Name)); - await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat); + await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize); if (_unitOfWork.HasChanges()) diff --git a/API/Services/Plus/LicenseService.cs b/API/Services/Plus/LicenseService.cs index ccbdd133fa..8bb845da36 100644 --- a/API/Services/Plus/LicenseService.cs +++ b/API/Services/Plus/LicenseService.cs @@ -128,15 +128,18 @@ private async Task RegisterLicense(string license, string email) /// Expected to be called at startup and on reoccurring basis public async Task ValidateLicenseStatus() { + var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License); try { var license = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey); - if (string.IsNullOrEmpty(license.Value)) return; + if (string.IsNullOrEmpty(license.Value)) { + await provider.SetAsync(CacheKey, false, _licenseCacheTimeout); + return; + } _logger.LogInformation("Validating Kavita+ License"); - var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License); - await provider.FlushAsync(); + await provider.FlushAsync(); var isValid = await IsLicenseValid(license.Value); await provider.SetAsync(CacheKey, isValid, _licenseCacheTimeout); @@ -145,6 +148,7 @@ public async Task ValidateLicenseStatus() catch (Exception ex) { _logger.LogError(ex, "There was an error talking with Kavita+ API for license validation. Rescheduling check in 30 mins"); + await provider.SetAsync(CacheKey, false, _licenseCacheTimeout); BackgroundJob.Schedule(() => ValidateLicenseStatus(), TimeSpan.FromMinutes(30)); } } diff --git a/API/Services/Plus/ScrobblingService.cs b/API/Services/Plus/ScrobblingService.cs index 075f535b04..7c9afaeee7 100644 --- a/API/Services/Plus/ScrobblingService.cs +++ b/API/Services/Plus/ScrobblingService.cs @@ -104,7 +104,7 @@ public ScrobblingService(IUnitOfWork unitOfWork, ITokenService tokenService, /// - /// + /// An automated job that will run against all user's tokens and validate if they are still active /// /// This service can validate without license check as the task which calls will be guarded /// @@ -115,6 +115,7 @@ public async Task CheckExternalAccessTokens() foreach (var user in users) { if (string.IsNullOrEmpty(user.AniListAccessToken) || !_tokenService.HasTokenExpired(user.AniListAccessToken)) continue; + _logger.LogInformation("User {UserName}'s AniList token has expired! They need to regenerate it for scrobbling to work", user.UserName); await _eventHub.SendMessageToAsync(MessageFactory.ScrobblingKeyExpired, MessageFactory.ScrobblingKeyExpiredEvent(ScrobbleProvider.AniList), user.Id); } @@ -184,17 +185,13 @@ private async Task GetTokenForProvider(int userId, ScrobbleProvider prov public async Task ScrobbleReviewUpdate(int userId, int seriesId, string reviewTitle, string reviewBody) { if (!await _licenseService.HasActiveLicense()) return; - var token = await GetTokenForProvider(userId, ScrobbleProvider.AniList); - if (await HasTokenExpired(token, ScrobbleProvider.AniList)) - { - throw new KavitaException(await _localizationService.Translate(userId, "unable-to-register-k+")); - } var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Metadata | SeriesIncludes.Library); if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist")); var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(series.LibraryId); - if (library is not {AllowScrobbling: true}) return; - if (library.Type == LibraryType.Comic) return; + + _logger.LogInformation("Processing Scrobbling review event for {UserId} on {SeriesName}", userId, series.Name); + if (await CheckIfCanScrobble(userId, seriesId, series)) return; var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id, ScrobbleEventType.Review); @@ -229,17 +226,12 @@ public async Task ScrobbleReviewUpdate(int userId, int seriesId, string reviewTi public async Task ScrobbleRatingUpdate(int userId, int seriesId, float rating) { if (!await _licenseService.HasActiveLicense()) return; - var token = await GetTokenForProvider(userId, ScrobbleProvider.AniList); - if (await HasTokenExpired(token, ScrobbleProvider.AniList)) - { - throw new KavitaException(await _localizationService.Translate(userId, "anilist-cred-expired")); - } var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Metadata | SeriesIncludes.Library); if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist")); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(series.LibraryId); - if (library is not {AllowScrobbling: true}) return; - if (library.Type == LibraryType.Comic) return; + + _logger.LogInformation("Processing Scrobbling rating event for {UserId} on {SeriesName}", userId, series.Name); + if (await CheckIfCanScrobble(userId, seriesId, series)) return; var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id, ScrobbleEventType.ScoreUpdated); @@ -273,22 +265,12 @@ public async Task ScrobbleRatingUpdate(int userId, int seriesId, float rating) public async Task ScrobbleReadingUpdate(int userId, int seriesId) { if (!await _licenseService.HasActiveLicense()) return; - var token = await GetTokenForProvider(userId, ScrobbleProvider.AniList); - if (await HasTokenExpired(token, ScrobbleProvider.AniList)) - { - throw new KavitaException(await _localizationService.Translate(userId, "anilist-cred-expired")); - } var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Metadata | SeriesIncludes.Library); if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist")); - if (await _unitOfWork.UserRepository.HasHoldOnSeries(userId, seriesId)) - { - _logger.LogInformation("Series {SeriesName} is on UserId {UserId}'s hold list. Not scrobbling", series.Name, userId); - return; - } - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(series.LibraryId); - if (library is not {AllowScrobbling: true}) return; - if (library.Type == LibraryType.Comic) return; + + _logger.LogInformation("Processing Scrobbling reading event for {UserId} on {SeriesName}", userId, series.Name); + if (await CheckIfCanScrobble(userId, seriesId, series)) return; var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id, ScrobbleEventType.ChapterRead); @@ -338,17 +320,12 @@ await _unitOfWork.AppUserProgressRepository.GetHighestFullyReadChapterForSeries( public async Task ScrobbleWantToReadUpdate(int userId, int seriesId, bool onWantToRead) { if (!await _licenseService.HasActiveLicense()) return; - var token = await GetTokenForProvider(userId, ScrobbleProvider.AniList); - if (await HasTokenExpired(token, ScrobbleProvider.AniList)) - { - throw new KavitaException(await _localizationService.Translate(userId, "anilist-cred-expired")); - } var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Metadata | SeriesIncludes.Library); if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist")); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(series.LibraryId); - if (library is not {AllowScrobbling: true}) return; - if (library.Type == LibraryType.Comic) return; + + _logger.LogInformation("Processing Scrobbling want-to-read event for {UserId} on {SeriesName}", userId, series.Name); + if (await CheckIfCanScrobble(userId, seriesId, series)) return; var existing = await _unitOfWork.ScrobbleRepository.Exists(userId, series.Id, onWantToRead ? ScrobbleEventType.AddWantToRead : ScrobbleEventType.RemoveWantToRead); @@ -369,6 +346,21 @@ public async Task ScrobbleWantToReadUpdate(int userId, int seriesId, bool onWant _logger.LogDebug("Added Scrobbling WantToRead update on {SeriesName} with Userid {UserId} ", series.Name, userId); } + private async Task CheckIfCanScrobble(int userId, int seriesId, Series series) + { + if (await _unitOfWork.UserRepository.HasHoldOnSeries(userId, seriesId)) + { + _logger.LogInformation("Series {SeriesName} is on UserId {UserId}'s hold list. Not scrobbling", series.Name, + userId); + return true; + } + + var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(series.LibraryId); + if (library is not {AllowScrobbling: true}) return true; + if (library.Type == LibraryType.Comic) return true; + return false; + } + private async Task GetRateLimit(string license, string aniListToken) { if (string.IsNullOrWhiteSpace(aniListToken)) return 0; diff --git a/API/Services/ReadingItemService.cs b/API/Services/ReadingItemService.cs index c163d45f07..86deed3939 100644 --- a/API/Services/ReadingItemService.cs +++ b/API/Services/ReadingItemService.cs @@ -10,7 +10,7 @@ public interface IReadingItemService { ComicInfo? GetComicInfo(string filePath); int GetNumberOfPages(string filePath, MangaFormat format); - string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat); + string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default); void Extract(string fileFilePath, string targetDirectory, MangaFormat format, int imageCount = 1); ParserInfo? ParseFile(string path, string rootPath, LibraryType type); } @@ -162,7 +162,7 @@ public int GetNumberOfPages(string filePath, MangaFormat format) } } - public string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat) + public string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default) { if (string.IsNullOrEmpty(filePath) || string.IsNullOrEmpty(fileName)) { @@ -172,10 +172,10 @@ public string GetCoverImage(string filePath, string fileName, MangaFormat format return format switch { - MangaFormat.Epub => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat), - MangaFormat.Archive => _archiveService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat), - MangaFormat.Image => _imageService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat), - MangaFormat.Pdf => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat), + MangaFormat.Epub => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size), + MangaFormat.Archive => _archiveService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size), + MangaFormat.Image => _imageService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size), + MangaFormat.Pdf => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size), _ => string.Empty }; } diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index df27791fe9..f70e6291fe 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -65,16 +65,19 @@ public SeriesService(IUnitOfWork unitOfWork, IEventHub eventHub, ITaskScheduler /// public static Chapter? GetFirstChapterForMetadata(Series series) { - var sortedVolumes = series.Volumes.OrderBy(v => v.Number, ChapterSortComparer.Default); + var sortedVolumes = series.Volumes + .Where(v => float.TryParse(v.Name, out var parsedValue) && parsedValue != 0.0f) + .OrderBy(v => float.TryParse(v.Name, out var parsedValue) ? parsedValue : float.MaxValue); var minVolumeNumber = sortedVolumes - .Where(v => v.Number != 0) - .MinBy(v => v.Number); + .MinBy(v => float.Parse(v.Name)); - var minChapter = series.Volumes - .SelectMany(v => v.Chapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default)) + + var allChapters = series.Volumes + .SelectMany(v => v.Chapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default)).ToList(); + var minChapter = allChapters .FirstOrDefault(); - if (minVolumeNumber != null && minChapter != null && float.Parse(minChapter.Number) > minVolumeNumber.Number) + if (minVolumeNumber != null && minChapter != null && float.TryParse(minChapter.Number, out var chapNum) && chapNum >= minVolumeNumber.Number) { return minVolumeNumber.Chapters.MinBy(c => float.Parse(c.Number), ChapterSortComparer.Default); } @@ -223,7 +226,15 @@ void HandleAddPerson(Person person) await _unitOfWork.CommitAsync(); // Trigger code to cleanup tags, collections, people, etc - await _taskScheduler.CleanupDbEntries(); + try + { + await _taskScheduler.CleanupDbEntries(); + } + catch (Exception ex) + { + _logger.LogError(ex, "There was an issue cleaning up DB entries. This may happen if Komf is spamming updates. Nightly cleanup will work"); + } + if (updateSeriesMetadataDto.CollectionTags == null) return true; foreach (var tag in updateSeriesMetadataDto.CollectionTags) diff --git a/API/Services/StatisticService.cs b/API/Services/StatisticService.cs index d23cc86c0f..2c8552749b 100644 --- a/API/Services/StatisticService.cs +++ b/API/Services/StatisticService.cs @@ -27,7 +27,7 @@ public interface IStatisticService Task> GetTopUsers(int days); Task> GetReadingHistory(int userId); Task>> ReadCountByDay(int userId = 0, int days = 0); - IEnumerable> GetDayBreakdown(); + IEnumerable> GetDayBreakdown(int userId = 0); IEnumerable> GetPagesReadCountByYear(int userId = 0); IEnumerable> GetWordsReadCountByYear(int userId = 0); Task UpdateServerStatistics(); @@ -411,11 +411,12 @@ public async Task>> ReadCountByDay(in return results.OrderBy(r => r.Value); } - public IEnumerable> GetDayBreakdown() + public IEnumerable> GetDayBreakdown(int userId) { return _context.AppUserProgresses .AsSplitQuery() .AsNoTracking() + .WhereIf(userId > 0, p => p.AppUserId == userId) .GroupBy(p => p.LastModified.DayOfWeek) .OrderBy(g => g.Key) .Select(g => new StatCount{ Value = g.Key, Count = g.Count() }) diff --git a/API/Services/Tasks/Scanner/LibraryWatcher.cs b/API/Services/Tasks/Scanner/LibraryWatcher.cs index 0875c3f524..6e844fbe3c 100644 --- a/API/Services/Tasks/Scanner/LibraryWatcher.cs +++ b/API/Services/Tasks/Scanner/LibraryWatcher.cs @@ -229,14 +229,20 @@ private async Task TurnOffWatching() public async Task ProcessChange(string filePath, bool isDirectoryChange = false) { var sw = Stopwatch.StartNew(); - _logger.LogDebug("[LibraryWatcher] Processing change of {FilePath}", filePath); + _logger.LogTrace("[LibraryWatcher] Processing change of {FilePath}", filePath); try { + // If the change occurs in a blacklisted folder path, then abort processing + if (Parser.Parser.HasBlacklistedFolderInPath(filePath)) + { + return; + } + // If not a directory change AND file is not an archive or book, ignore if (!isDirectoryChange && !(Parser.Parser.IsArchive(filePath) || Parser.Parser.IsBook(filePath))) { - _logger.LogDebug("[LibraryWatcher] Change from {FilePath} is not an archive or book, ignoring change", filePath); + _logger.LogTrace("[LibraryWatcher] Change from {FilePath} is not an archive or book, ignoring change", filePath); return; } @@ -248,10 +254,10 @@ public async Task ProcessChange(string filePath, bool isDirectoryChange = false) .ToList(); var fullPath = GetFolder(filePath, libraryFolders); - _logger.LogDebug("Folder path: {FolderPath}", fullPath); + _logger.LogTrace("Folder path: {FolderPath}", fullPath); if (string.IsNullOrEmpty(fullPath)) { - _logger.LogDebug("[LibraryWatcher] Change from {FilePath} could not find root level folder, ignoring change", filePath); + _logger.LogTrace("[LibraryWatcher] Change from {FilePath} could not find root level folder, ignoring change", filePath); return; } diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index f30da8a478..d6c43d8c28 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -146,7 +146,6 @@ await _eventHub.SendMessageAsync(MessageFactory.Error, _logger.LogInformation("[ScannerService] Processing series {SeriesName}", series.OriginalName); // parsedInfos[0] is not the first volume or chapter. We need to find it using a ComicInfo check (as it uses firstParsedInfo for series sort) - // BUG: This check doesn't work for Books, as books usually have metadata on all files. (#2167) var firstParsedInfo = parsedInfos.FirstOrDefault(p => p.ComicInfo != null, firstInfo); UpdateVolumes(series, parsedInfos, forceUpdate); @@ -231,7 +230,8 @@ await _eventHub.SendMessageAsync(MessageFactory.SeriesAdded, _logger.LogError(ex, "[ScannerService] There was an exception updating series for {SeriesName}", series.Name); } - await _metadataService.GenerateCoversForSeries(series, (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs); + var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); + await _metadataService.GenerateCoversForSeries(series, settings.EncodeMediaAs, settings.CoverImageSize); EnqueuePostSeriesProcessTasks(series.LibraryId, series.Id); } diff --git a/API/Services/Tasks/StatsService.cs b/API/Services/Tasks/StatsService.cs index e4f997767a..bc14967a39 100644 --- a/API/Services/Tasks/StatsService.cs +++ b/API/Services/Tasks/StatsService.cs @@ -35,7 +35,7 @@ public class StatsService : IStatsService private readonly IUnitOfWork _unitOfWork; private readonly DataContext _context; private readonly IStatisticService _statisticService; - private const string ApiUrl = "https://stats.kavitareader.com"; + private const string ApiUrl = "https://stats.kavitareader.com"; // "" public StatsService(ILogger logger, IUnitOfWork unitOfWork, DataContext context, IStatisticService statisticService) { diff --git a/API/Services/TokenService.cs b/API/Services/TokenService.cs index cce1a05405..b6b0ade471 100644 --- a/API/Services/TokenService.cs +++ b/API/Services/TokenService.cs @@ -54,7 +54,6 @@ public async Task CreateToken(AppUser user) }; var roles = await _userManager.GetRolesAsync(user); - claims.AddRange(roles.Select(role => new Claim(Role, role))); var credentials = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature); diff --git a/API/SignalR/Presence/PresenceTracker.cs b/API/SignalR/Presence/PresenceTracker.cs index 57413442c4..fc4970a52b 100644 --- a/API/SignalR/Presence/PresenceTracker.cs +++ b/API/SignalR/Presence/PresenceTracker.cs @@ -57,18 +57,6 @@ public async Task UserConnected(int userId, string connectionId) }); } } - - // Update the last active for the user - try - { - user.UpdateLastActive(); - _unitOfWork.UserRepository.Update(user); - await _unitOfWork.CommitAsync(); - } - catch (Exception) - { - // Swallow the exception - } } public Task UserDisconnected(int userId, string connectionId) diff --git a/API/Startup.cs b/API/Startup.cs index ed68f050e5..5f4ec69d70 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -317,7 +317,7 @@ public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJo .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials() // For SignalR token query param - .WithOrigins("http://localhost:4200", $"http://{GetLocalIpAddress()}:4200", $"http://{GetLocalIpAddress()}:5000", "https://kavita.majora2007.duckdns.org") + .WithOrigins("http://localhost:4200", $"http://{GetLocalIpAddress()}:4200", $"http://{GetLocalIpAddress()}:5000") .WithExposedHeaders("Content-Disposition", "Pagination")); } else @@ -327,7 +327,6 @@ public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJo .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials() // For SignalR token query param - .WithOrigins("https://kavita.majora2007.duckdns.org") .WithExposedHeaders("Content-Disposition", "Pagination")); } diff --git a/Dockerfile b/Dockerfile index a9eafdd7a7..0b4174d096 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ COPY _output/*.tar.gz /files/ COPY UI/Web/dist /files/wwwroot COPY copy_runtime.sh /copy_runtime.sh RUN /copy_runtime.sh +RUN chmod +x /Kavita/Kavita #Production image FROM ubuntu:focal @@ -25,6 +26,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh EXPOSE 5000 diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 4ac605ee57..0b0185c448 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -4,7 +4,7 @@ net7.0 kavitareader.com Kavita - 0.7.7.0 + 0.7.8.0 en true @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index 5d4594bb74..67a882de16 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -23,7 +23,7 @@ "@iplab/ngx-file-upload": "^16.0.1", "@microsoft/signalr": "^7.0.10", "@ng-bootstrap/ng-bootstrap": "^15.1.1", - "@ngneat/transloco": "^5.0.6", + "@ngneat/transloco": "^5.0.7", "@ngneat/transloco-locale": "^5.1.1", "@ngneat/transloco-persist-lang": "^5.0.0", "@ngneat/transloco-persist-translations": "^5.0.0", @@ -37,6 +37,7 @@ "file-saver": "^2.0.5", "lazysizes": "^5.3.2", "ng-circle-progress": "^1.7.1", + "ng-select2-component": "^13.0.6", "ngx-color-picker": "^14.0.0", "ngx-extended-pdf-viewer": "^16.2.16", "ngx-file-drop": "^16.0.0", @@ -3169,9 +3170,9 @@ } }, "node_modules/@ngneat/transloco": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ngneat/transloco/-/transloco-5.0.6.tgz", - "integrity": "sha512-pt0jiU0co0nT72bhodT9ervBvSgl1jVUrTbLsHwjtP3WoJZxfOmXN21j5MSA/GJFRkolceI8+yWqtG7jux+WDg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ngneat/transloco/-/transloco-5.0.7.tgz", + "integrity": "sha512-x1c2e+7cOYPPVFPgqGcN3R6d7f18a4sMHzxsCamcxS2w7vWXcEzWKZ8JcI1TdpxrM+RKuj2NRfEEcr1HjAI/4w==", "dependencies": { "@ngneat/transloco-utils": "^5.0.0", "flat": "5.0.2", @@ -10556,6 +10557,20 @@ "rxjs": ">=6.4.0" } }, + "node_modules/ng-select2-component": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-13.0.6.tgz", + "integrity": "sha512-CiAelglSz2aeYy0BiXRi32zc49Mq27+J1eDzTrXmf2o50MvNo3asS3NRVQcnSldo/zLcJafWCMueVfjVaV1etw==", + "dependencies": { + "ngx-infinite-scroll": ">=16.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": ">=16.1.0", + "@angular/common": ">=16.1.0", + "@angular/core": ">=16.1.0" + } + }, "node_modules/ngx-color-picker": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-14.0.0.tgz", @@ -10598,6 +10613,18 @@ "@angular/core": ">=14.0.0" } }, + "node_modules/ngx-infinite-scroll": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-16.0.0.tgz", + "integrity": "sha512-bzyNYd+wVlUUxcopRVr2DAa81eEc8vITtKVvb+c7R1uy8hWPTlxOEXf3L1qA4FMwTEzCQ9b37TXzlJji3qBy+A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0 <17.0.0", + "@angular/core": ">=16.0.0 <17.0.0" + } + }, "node_modules/ngx-slider-v2": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/ngx-slider-v2/-/ngx-slider-v2-16.0.2.tgz", diff --git a/UI/Web/package.json b/UI/Web/package.json index 1e273421fa..05da6248c8 100644 --- a/UI/Web/package.json +++ b/UI/Web/package.json @@ -28,7 +28,7 @@ "@iplab/ngx-file-upload": "^16.0.1", "@microsoft/signalr": "^7.0.10", "@ng-bootstrap/ng-bootstrap": "^15.1.1", - "@ngneat/transloco": "^5.0.6", + "@ngneat/transloco": "^5.0.7", "@ngneat/transloco-locale": "^5.1.1", "@ngneat/transloco-persist-lang": "^5.0.0", "@ngneat/transloco-persist-translations": "^5.0.0", @@ -42,6 +42,7 @@ "file-saver": "^2.0.5", "lazysizes": "^5.3.2", "ng-circle-progress": "^1.7.1", + "ng-select2-component": "^13.0.6", "ngx-color-picker": "^14.0.0", "ngx-extended-pdf-viewer": "^16.2.16", "ngx-file-drop": "^16.0.0", diff --git a/UI/Web/src/app/_models/metadata/language.ts b/UI/Web/src/app/_models/metadata/language.ts index c88ff3939a..e8f606bec4 100644 --- a/UI/Web/src/app/_models/metadata/language.ts +++ b/UI/Web/src/app/_models/metadata/language.ts @@ -1,4 +1,5 @@ export interface Language { isoCode: string; title: string; -} \ No newline at end of file +} + diff --git a/UI/Web/src/app/_models/metadata/series-filter.ts b/UI/Web/src/app/_models/metadata/series-filter.ts index 18ef0257cb..87f064f2a7 100644 --- a/UI/Web/src/app/_models/metadata/series-filter.ts +++ b/UI/Web/src/app/_models/metadata/series-filter.ts @@ -1,4 +1,6 @@ import { MangaFormat } from "../manga-format"; +import { SeriesFilterV2 } from "./v2/series-filter-v2"; +import {FilterField} from "./v2/filter-field"; export interface FilterItem { title: string; @@ -6,38 +8,6 @@ export interface FilterItem { selected: boolean; } -export interface Range { - min: T; - max: T; -} - -export interface SeriesFilter { - formats: Array; - libraries: Array, - readStatus: ReadStatus; - genres: Array; - writers: Array; - artists: Array; - penciller: Array; - inker: Array; - colorist: Array; - letterer: Array; - coverArtist: Array; - editor: Array; - publisher: Array; - character: Array; - translators: Array; - collectionTags: Array; - rating: number; - ageRating: Array; - sortOptions: SortOptions | null; - tags: Array; - languages: Array; - publicationStatus: Array; - seriesNameQuery: string; - releaseYearRange: Range | null; -} - export interface SortOptions { sortField: SortField; isAscending: boolean; @@ -52,11 +22,9 @@ export enum SortField { ReleaseYear = 6, } -export interface ReadStatus { - notRead: boolean, - inProgress: boolean, - read: boolean, -} +export const allSortFields = Object.keys(SortField) + .filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0) + .map(key => parseInt(key, 10)) as SortField[]; export const mangaFormatFilters = [ { @@ -82,7 +50,7 @@ export const mangaFormatFilters = [ ]; export interface FilterEvent { - filter: SeriesFilter; + filterV2: SeriesFilterV2; isFirst: boolean; } diff --git a/UI/Web/src/app/_models/metadata/v2/filter-combination.ts b/UI/Web/src/app/_models/metadata/v2/filter-combination.ts new file mode 100644 index 0000000000..05f1ee6683 --- /dev/null +++ b/UI/Web/src/app/_models/metadata/v2/filter-combination.ts @@ -0,0 +1,4 @@ +export enum FilterCombination { + Or = 0, + And = 1 +} diff --git a/UI/Web/src/app/_models/metadata/v2/filter-comparison.ts b/UI/Web/src/app/_models/metadata/v2/filter-comparison.ts new file mode 100644 index 0000000000..fa30dc7864 --- /dev/null +++ b/UI/Web/src/app/_models/metadata/v2/filter-comparison.ts @@ -0,0 +1,46 @@ +export enum FilterComparison { + Equal = 0, + GreaterThan =1, + GreaterThanEqual = 2, + LessThan = 3, + LessThanEqual = 4, + /// + /// + /// + /// Only works with IList + Contains = 5, + MustContains = 6, + /// + /// Performs a LIKE %value% + /// + Matches = 7, + NotContains = 8, + /// + /// Not Equal to + /// + NotEqual = 9, + /// + /// String starts with + /// + BeginsWith = 10, + /// + /// String ends with + /// + EndsWith = 11, + /// + /// Is Date before X + /// + IsBefore = 12, + /// + /// Is Date after X + /// + IsAfter = 13, + /// + /// Is Date between now and X seconds ago + /// + IsInLast = 14, + /// + /// Is Date not between now and X seconds ago + /// + IsNotInLast = 15, +} diff --git a/UI/Web/src/app/_models/metadata/v2/filter-field.ts b/UI/Web/src/app/_models/metadata/v2/filter-field.ts new file mode 100644 index 0000000000..27335b7ca0 --- /dev/null +++ b/UI/Web/src/app/_models/metadata/v2/filter-field.ts @@ -0,0 +1,35 @@ +export enum FilterField +{ + None = -1, + Summary = 0, + SeriesName = 1, + PublicationStatus = 2, + Languages = 3, + AgeRating = 4, + UserRating = 5, + Tags = 6, + CollectionTags = 7, + Translators = 8, + Characters = 9, + Publisher = 10, + Editor = 11, + CoverArtist = 12, + Letterer = 13, + Colorist = 14, + Inker = 15, + Penciller = 16, + Writers = 17, + Genres = 18, + Libraries = 19, + ReadProgress = 20, + Formats = 21, + ReleaseYear = 22, + ReadTime = 23, + Path = 24, + FilePath = 25 +} + +export const allFields = Object.keys(FilterField) + .filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0) + .map(key => parseInt(key, 10)) + .sort((a, b) => a - b) as FilterField[]; diff --git a/UI/Web/src/app/_models/metadata/v2/filter-statement.ts b/UI/Web/src/app/_models/metadata/v2/filter-statement.ts new file mode 100644 index 0000000000..d031927a20 --- /dev/null +++ b/UI/Web/src/app/_models/metadata/v2/filter-statement.ts @@ -0,0 +1,8 @@ +import { FilterComparison } from "./filter-comparison"; +import { FilterField } from "./filter-field"; + +export interface FilterStatement { + comparison: FilterComparison; + field: FilterField; + value: string; +} \ No newline at end of file diff --git a/UI/Web/src/app/_models/metadata/v2/series-filter-v2.ts b/UI/Web/src/app/_models/metadata/v2/series-filter-v2.ts new file mode 100644 index 0000000000..c13244644c --- /dev/null +++ b/UI/Web/src/app/_models/metadata/v2/series-filter-v2.ts @@ -0,0 +1,11 @@ +import { SortOptions } from "../series-filter"; +import {FilterStatement} from "./filter-statement"; +import {FilterCombination} from "./filter-combination"; + +export interface SeriesFilterV2 { + name?: string; + statements: Array; + combination: FilterCombination; + sortOptions?: SortOptions; + limitTo: number; +} diff --git a/UI/Web/src/app/_models/readers/page-bookmark.ts b/UI/Web/src/app/_models/readers/page-bookmark.ts index e47ef0a068..68feee1186 100644 --- a/UI/Web/src/app/_models/readers/page-bookmark.ts +++ b/UI/Web/src/app/_models/readers/page-bookmark.ts @@ -1,8 +1,10 @@ +import {Series} from "../series"; + export interface PageBookmark { id: number; page: number; seriesId: number; volumeId: number; chapterId: number; - fileName: string; -} \ No newline at end of file + series: Series; +} diff --git a/UI/Web/src/app/_services/account.service.ts b/UI/Web/src/app/_services/account.service.ts index 86cf72618b..786a410841 100644 --- a/UI/Web/src/app/_services/account.service.ts +++ b/UI/Web/src/app/_services/account.service.ts @@ -55,7 +55,6 @@ export class AccountService { private messageHub: MessageHubService, private themeService: ThemeService) { messageHub.messages$.pipe(filter(evt => evt.event === EVENTS.UserUpdate), map(evt => evt.payload as UserUpdateEvent), - tap(u => console.log('user update: ', u)), filter(userUpdateEvent => userUpdateEvent.userName === this.currentUser?.username), switchMap(() => this.refreshAccount())) .subscribe(() => {}); @@ -307,7 +306,6 @@ export class AccountService { private refreshAccount() { - console.log('Refreshing account'); if (this.currentUser === null || this.currentUser === undefined) return of(); return this.httpClient.get(this.baseUrl + 'account/refresh-account').pipe(map((user: User) => { if (user) { diff --git a/UI/Web/src/app/_services/action-factory.service.ts b/UI/Web/src/app/_services/action-factory.service.ts index f6660482eb..393aade6c5 100644 --- a/UI/Web/src/app/_services/action-factory.service.ts +++ b/UI/Web/src/app/_services/action-factory.service.ts @@ -92,6 +92,8 @@ export enum Action { * Removes the Series from On Deck inclusion */ RemoveFromOnDeck = 19, + AddRuleGroup = 20, + RemoveRuleGroup = 21 } export interface ActionItem { @@ -178,6 +180,15 @@ export class ActionFactoryService { return this.applyCallbackToList(this.bookmarkActions, callback); } + getMetadataFilterActions(callback: (action: ActionItem, data: any) => void) { + const actions = [ + {title: 'add-rule-group-and', action: Action.AddRuleGroup, requiresAdmin: false, children: [], callback: this.dummyCallback}, + {title: 'add-rule-group-or', action: Action.AddRuleGroup, requiresAdmin: false, children: [], callback: this.dummyCallback}, + {title: 'remove-rule-group', action: Action.RemoveRuleGroup, requiresAdmin: false, children: [], callback: this.dummyCallback}, + ]; + return this.applyCallbackToList(actions, callback); + } + dummyCallback(action: ActionItem, data: any) {} filterSendToAction(actions: Array>, chapter: Chapter) { @@ -236,6 +247,14 @@ export class ActionFactoryService { requiresAdmin: true, children: [], }, + { + action: Action.Delete, + title: 'delete', + callback: this.dummyCallback, + requiresAdmin: false, + class: 'danger', + children: [], + }, ]; this.seriesActions = [ diff --git a/UI/Web/src/app/_services/collection-tag.service.ts b/UI/Web/src/app/_services/collection-tag.service.ts index 2f19352aba..3e4b8b508c 100644 --- a/UI/Web/src/app/_services/collection-tag.service.ts +++ b/UI/Web/src/app/_services/collection-tag.service.ts @@ -41,4 +41,8 @@ export class CollectionTagService { tagNameExists(name: string) { return this.httpClient.get(this.baseUrl + 'collection/name-exists?name=' + name); } + + deleteTag(tagId: number) { + return this.httpClient.delete(this.baseUrl + 'collection?tagId=' + tagId, TextResonse); + } } diff --git a/UI/Web/src/app/_services/metadata.service.ts b/UI/Web/src/app/_services/metadata.service.ts index da34eafd50..2e8214fb97 100644 --- a/UI/Web/src/app/_services/metadata.service.ts +++ b/UI/Web/src/app/_services/metadata.service.ts @@ -1,16 +1,23 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { of } from 'rxjs'; +import {HttpClient} from '@angular/common/http'; +import {Injectable} from '@angular/core'; import {map, tap} from 'rxjs/operators'; -import { environment } from 'src/environments/environment'; -import { Genre } from '../_models/metadata/genre'; -import { AgeRating } from '../_models/metadata/age-rating'; -import { AgeRatingDto } from '../_models/metadata/age-rating-dto'; -import { Language } from '../_models/metadata/language'; -import { PublicationStatusDto } from '../_models/metadata/publication-status-dto'; -import { Person } from '../_models/metadata/person'; -import { Tag } from '../_models/tag'; -import { TextResonse } from '../_types/text-response'; +import {of, ReplaySubject, switchMap} from 'rxjs'; +import {environment} from 'src/environments/environment'; +import {Genre} from '../_models/metadata/genre'; +import {AgeRating} from '../_models/metadata/age-rating'; +import {AgeRatingDto} from '../_models/metadata/age-rating-dto'; +import {Language} from '../_models/metadata/language'; +import {PublicationStatusDto} from '../_models/metadata/publication-status-dto'; +import {Person, PersonRole} from '../_models/metadata/person'; +import {Tag} from '../_models/tag'; +import {TextResonse} from '../_types/text-response'; +import {FilterComparison} from '../_models/metadata/v2/filter-comparison'; +import {FilterField} from '../_models/metadata/v2/filter-field'; +import {Router} from "@angular/router"; +import {SortField} from "../_models/metadata/series-filter"; +import {FilterCombination} from "../_models/metadata/v2/filter-combination"; +import {SeriesFilterV2} from "../_models/metadata/v2/series-filter-v2"; +import {FilterStatement} from "../_models/metadata/v2/filter-statement"; @Injectable({ providedIn: 'root' @@ -18,25 +25,9 @@ import { TextResonse } from '../_types/text-response'; export class MetadataService { baseUrl = environment.apiUrl; - - private ageRatingTypes: {[key: number]: string} | undefined = undefined; private validLanguages: Array = []; - constructor(private httpClient: HttpClient) { } - - getAgeRating(ageRating: AgeRating) { - if (this.ageRatingTypes != undefined && this.ageRatingTypes.hasOwnProperty(ageRating)) { - return of(this.ageRatingTypes[ageRating]); - } - return this.httpClient.get(this.baseUrl + 'series/age-rating?ageRating=' + ageRating, TextResonse).pipe(map(ratingString => { - if (this.ageRatingTypes === undefined) { - this.ageRatingTypes = {}; - } - - this.ageRatingTypes[ageRating] = ratingString; - return this.ageRatingTypes[ageRating]; - })); - } + constructor(private httpClient: HttpClient, private router: Router) { } getAllAgeRatings(libraries?: Array) { let method = 'metadata/age-ratings' @@ -78,6 +69,7 @@ export class MetadataService { return this.httpClient.get>(this.baseUrl + method); } + /** * All the potential language tags there can be */ @@ -97,7 +89,37 @@ export class MetadataService { return this.httpClient.get>(this.baseUrl + method); } - getChapterSummary(chapterId: number) { - return this.httpClient.get(this.baseUrl + 'metadata/chapter-summary?chapterId=' + chapterId, TextResonse); + getAllPeopleByRole(role: PersonRole) { + return this.httpClient.get>(this.baseUrl + 'metadata/people-by-role?role=' + role); + } + + // getChapterSummary(chapterId: number) { + // return this.httpClient.get(this.baseUrl + 'metadata/chapter-summary?chapterId=' + chapterId, TextResonse); + // } + + createDefaultFilterDto(): SeriesFilterV2 { + return { + statements: [] as FilterStatement[], + combination: FilterCombination.And, + limitTo: 0, + sortOptions: { + isAscending: true, + sortField: SortField.SortName + } + }; + } + + createDefaultFilterStatement(field: FilterField = FilterField.SeriesName, comparison = FilterComparison.Equal, value = '') { + return { + comparison: comparison, + field: field, + value: value + }; + } + + updateFilter(arr: Array, index: number, filterStmt: FilterStatement) { + arr[index].comparison = filterStmt.comparison; + arr[index].field = filterStmt.field; + arr[index].value = filterStmt.value ? filterStmt.value + '' : ''; } } diff --git a/UI/Web/src/app/_services/reader.service.ts b/UI/Web/src/app/_services/reader.service.ts index 17216a90f8..7c6547abff 100644 --- a/UI/Web/src/app/_services/reader.service.ts +++ b/UI/Web/src/app/_services/reader.service.ts @@ -10,7 +10,6 @@ import { MangaFormat } from '../_models/manga-format'; import { BookmarkInfo } from '../_models/manga-reader/bookmark-info'; import { PageBookmark } from '../_models/readers/page-bookmark'; import { ProgressBookmark } from '../_models/readers/progress-bookmark'; -import { SeriesFilter } from '../_models/metadata/series-filter'; import { UtilityService } from '../shared/_services/utility.service'; import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service'; import { FileDimension } from '../manga-reader/_models/file-dimension'; @@ -19,6 +18,7 @@ import { TextResonse } from '../_types/text-response'; import { AccountService } from './account.service'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {PersonalToC} from "../_models/readers/personal-toc"; +import {SeriesFilterV2} from "../_models/metadata/v2/series-filter-v2"; export const CHAPTER_ID_DOESNT_EXIST = -1; export const CHAPTER_ID_NOT_FETCHED = -2; @@ -70,12 +70,8 @@ export class ReaderService { return this.httpClient.post(this.baseUrl + 'reader/unbookmark', {seriesId, volumeId, chapterId, page}); } - getAllBookmarks(filter: SeriesFilter | undefined) { - let params = new HttpParams(); - params = this.utilityService.addPaginationIfExists(params, undefined, undefined); - const data = this.filterUtilityService.createSeriesFilter(filter); - - return this.httpClient.post(this.baseUrl + 'reader/all-bookmarks', data); + getAllBookmarks(filter: SeriesFilterV2 | undefined) { + return this.httpClient.post(this.baseUrl + 'reader/all-bookmarks', filter); } getBookmarks(chapterId: number) { diff --git a/UI/Web/src/app/_services/series.service.ts b/UI/Web/src/app/_services/series.service.ts index 659125e411..dd021dbe0f 100644 --- a/UI/Web/src/app/_services/series.service.ts +++ b/UI/Web/src/app/_services/series.service.ts @@ -12,12 +12,12 @@ import { PaginatedResult } from '../_models/pagination'; import { Series } from '../_models/series'; import { RelatedSeries } from '../_models/series-detail/related-series'; import { SeriesDetail } from '../_models/series-detail/series-detail'; -import { SeriesFilter } from '../_models/metadata/series-filter'; import { SeriesGroup } from '../_models/series-group'; import { SeriesMetadata } from '../_models/metadata/series-metadata'; import { Volume } from '../_models/volume'; import { ImageService } from './image.service'; import { TextResonse } from '../_types/text-response'; +import { SeriesFilterV2 } from '../_models/metadata/v2/series-filter-v2'; import {UserReview} from "../_single-module/review-card/user-review"; import {Rating} from "../_models/rating"; import {Recommendation} from "../_models/series-detail/recommendation"; @@ -32,26 +32,26 @@ export class SeriesService { paginatedSeriesForTagsResults: PaginatedResult = new PaginatedResult(); constructor(private httpClient: HttpClient, private imageService: ImageService, - private utilityService: UtilityService, private filterUtilityService: FilterUtilitiesService) { } + private utilityService: UtilityService) { } - getAllSeries(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { + getAllSeriesV2(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilterV2) { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); - const data = this.filterUtilityService.createSeriesFilter(filter); + const data = filter || {}; - return this.httpClient.post>(this.baseUrl + 'series/all', data, {observe: 'response', params}).pipe( - map((response: any) => { - return this.utilityService.createPaginatedResult(response, this.paginatedResults); - }) + return this.httpClient.post>(this.baseUrl + 'series/all-v2', data, {observe: 'response', params}).pipe( + map((response: any) => { + return this.utilityService.createPaginatedResult(response, this.paginatedResults); + }) ); } - getSeriesForLibrary(libraryId: number, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { + getSeriesForLibraryV2(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilterV2) { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); - const data = this.filterUtilityService.createSeriesFilter(filter); + const data = filter || {}; - return this.httpClient.post>(this.baseUrl + 'series?libraryId=' + libraryId, data, {observe: 'response', params}).pipe( + return this.httpClient.post>(this.baseUrl + 'series/v2', data, {observe: 'response', params}).pipe( map((response: any) => { return this.utilityService.createPaginatedResult(response, this.paginatedResults); }) @@ -102,12 +102,12 @@ export class SeriesService { return this.httpClient.post(this.baseUrl + 'reader/mark-unread', {seriesId}); } - getRecentlyAdded(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { - const data = this.filterUtilityService.createSeriesFilter(filter); + getRecentlyAdded(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilterV2) { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); - return this.httpClient.post(this.baseUrl + 'series/recently-added?libraryId=' + libraryId, data, {observe: 'response', params}).pipe( + const data = filter || {}; + return this.httpClient.post(this.baseUrl + 'series/recently-added-v2', data, {observe: 'response', params}).pipe( map(response => { return this.utilityService.createPaginatedResult(response, new PaginatedResult()); }) @@ -118,13 +118,12 @@ export class SeriesService { return this.httpClient.post(this.baseUrl + 'series/recently-updated-series', {}); } - getWantToRead(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter): Observable> { - const data = this.filterUtilityService.createSeriesFilter(filter); - + getWantToRead(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilterV2): Observable> { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); + const data = filter || {}; - return this.httpClient.post(this.baseUrl + 'want-to-read/', data, {observe: 'response', params}).pipe( + return this.httpClient.post(this.baseUrl + 'want-to-read/v2', data, {observe: 'response', params}).pipe( map(response => { return this.utilityService.createPaginatedResult(response, new PaginatedResult()); })); @@ -137,11 +136,10 @@ export class SeriesService { })); } - getOnDeck(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { - const data = this.filterUtilityService.createSeriesFilter(filter); - + getOnDeck(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilterV2) { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); + const data = filter || {}; return this.httpClient.post(this.baseUrl + 'series/on-deck?libraryId=' + libraryId, data, {observe: 'response', params}).pipe( map(response => { diff --git a/UI/Web/src/app/_services/statistics.service.ts b/UI/Web/src/app/_services/statistics.service.ts index 51fe3f0253..ff87fa2c45 100644 --- a/UI/Web/src/app/_services/statistics.service.ts +++ b/UI/Web/src/app/_services/statistics.service.ts @@ -112,7 +112,7 @@ export class StatisticsService { return this.httpClient.get>(this.baseUrl + 'stats/reading-count-by-day?userId=' + userId + '&days=' + days); } - getDayBreakdown() { - return this.httpClient.get>>(this.baseUrl + 'stats/day-breakdown'); + getDayBreakdown( userId = 0) { + return this.httpClient.get>>(this.baseUrl + 'stats/day-breakdown?userId=' + userId); } } diff --git a/UI/Web/src/app/cards/dynamic-list.pipe.ts b/UI/Web/src/app/_single-module/card-actionables/_pipes/dynamic-list.pipe.ts similarity index 100% rename from UI/Web/src/app/cards/dynamic-list.pipe.ts rename to UI/Web/src/app/_single-module/card-actionables/_pipes/dynamic-list.pipe.ts diff --git a/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.html b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.html similarity index 100% rename from UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.html rename to UI/Web/src/app/_single-module/card-actionables/card-actionables.component.html diff --git a/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.scss b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.scss similarity index 100% rename from UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.scss rename to UI/Web/src/app/_single-module/card-actionables/card-actionables.component.scss diff --git a/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.ts b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts similarity index 98% rename from UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.ts rename to UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts index ab9fa9ebd5..e9e9952dcf 100644 --- a/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.ts +++ b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.ts @@ -4,8 +4,8 @@ import { take } from 'rxjs'; import { AccountService } from 'src/app/_services/account.service'; import { Action, ActionItem } from 'src/app/_services/action-factory.service'; import {CommonModule} from "@angular/common"; -import {DynamicListPipe} from "../../dynamic-list.pipe"; import {TranslocoDirective} from "@ngneat/transloco"; +import {DynamicListPipe} from "./_pipes/dynamic-list.pipe"; @Component({ selector: 'app-card-actionables', diff --git a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html b/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html index d00d34fa49..00c60cc80b 100644 --- a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html +++ b/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html @@ -1,28 +1,30 @@ -
- - + - -
+ + +
+ + diff --git a/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts b/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts index 2e248c2846..4dcbfc82d0 100644 --- a/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts +++ b/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts @@ -34,7 +34,6 @@ export class SpoilerComponent implements OnInit{ ngOnInit() { this.isCollapsed = true; this.cdRef.markForCheck(); - console.log('html: ', this.html) } diff --git a/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.html b/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.html index d3a544e23a..00902d6c1b 100644 --- a/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.html +++ b/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.html @@ -62,7 +62,7 @@
{{t('title')}}
- {{t('volume-and-chapter-num', {v: item.volumeNumber, c: item.chapterNumber})}} + {{t('volume-and-chapter-num', {v: item.volumeNumber, n: item.chapterNumber})}} {{t('rating', {r: item.rating})}} diff --git a/UI/Web/src/app/admin/_models/cover-image-size.ts b/UI/Web/src/app/admin/_models/cover-image-size.ts new file mode 100644 index 0000000000..6a58de8ba7 --- /dev/null +++ b/UI/Web/src/app/admin/_models/cover-image-size.ts @@ -0,0 +1,14 @@ +export enum CoverImageSize { + Default = 1, + Medium = 2, + Large = 3, + XLarge = 4 +} + +export const CoverImageSizes = + [ + {value: CoverImageSize.Default, title: 'cover-image-size.default'}, + {value: CoverImageSize.Medium, title: 'cover-image-size.medium'}, + {value: CoverImageSize.Large, title: 'cover-image-size.large'}, + {value: CoverImageSize.XLarge, title: 'cover-image-size.xlarge'} + ]; diff --git a/UI/Web/src/app/admin/_models/server-settings.ts b/UI/Web/src/app/admin/_models/server-settings.ts index b72396a8ca..e58aa5190b 100644 --- a/UI/Web/src/app/admin/_models/server-settings.ts +++ b/UI/Web/src/app/admin/_models/server-settings.ts @@ -1,4 +1,5 @@ import { EncodeFormat } from "./encode-format"; +import {CoverImageSize} from "./cover-image-size"; export interface ServerSettings { cacheDirectory: string; @@ -20,4 +21,5 @@ export interface ServerSettings { cacheSize: number; onDeckProgressDays: number; onDeckUpdateDays: number; + coverImageSize: CoverImageSize; } diff --git a/UI/Web/src/app/admin/invite-user/invite-user.component.html b/UI/Web/src/app/admin/invite-user/invite-user.component.html index 7a9220bfad..124b91571a 100644 --- a/UI/Web/src/app/admin/invite-user/invite-user.component.html +++ b/UI/Web/src/app/admin/invite-user/invite-user.component.html @@ -42,7 +42,7 @@

{{t('setup-user-title')}}

{{t('setup-user-description')}}

- +
diff --git a/UI/Web/src/app/admin/library-selector/library-selector.component.html b/UI/Web/src/app/admin/library-selector/library-selector.component.html index 8a252bec8a..68fe1980d2 100644 --- a/UI/Web/src/app/admin/library-selector/library-selector.component.html +++ b/UI/Web/src/app/admin/library-selector/library-selector.component.html @@ -4,7 +4,7 @@

{{t('title')}}

- +
  • diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html index 7d78644d3f..4b01b5b117 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html @@ -7,7 +7,7 @@
    {{t('encode-as-warning')}}

    -
    +
    {{t('encode-as-tooltip')}} @@ -16,6 +16,16 @@
    + +
    + + + {{t('cover-image-size-tooltip')}} + + +
    diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts index 62ff9094b9..f1de0ed378 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts @@ -21,7 +21,8 @@ import {EncodeFormats} from '../_models/encode-format'; import {ManageScrobbleErrorsComponent} from '../manage-scrobble-errors/manage-scrobble-errors.component'; import {ManageAlertsComponent} from '../manage-alerts/manage-alerts.component'; import {NgFor, NgIf, NgTemplateOutlet} from '@angular/common'; -import {TranslocoDirective, TranslocoService} from "@ngneat/transloco"; +import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco"; +import { CoverImageSizes } from '../_models/cover-image-size'; @Component({ selector: 'app-manage-media-settings', @@ -38,6 +39,11 @@ export class ManageMediaSettingsComponent implements OnInit { alertCount: number = 0; scrobbleCount: number = 0; + coverImageSizes = CoverImageSizes.map(o => { + const newObj = {...o}; + newObj.title = translate(o.title); + return newObj; + }) private readonly translocoService = inject(TranslocoService); private readonly cdRef = inject(ChangeDetectorRef); @@ -51,6 +57,7 @@ export class ManageMediaSettingsComponent implements OnInit { this.serverSettings = settings; this.settingsForm.addControl('encodeMediaAs', new FormControl(this.serverSettings.encodeMediaAs, [Validators.required])); this.settingsForm.addControl('bookmarksDirectory', new FormControl(this.serverSettings.bookmarksDirectory, [Validators.required])); + this.settingsForm.addControl('coverImageSize', new FormControl(this.serverSettings.coverImageSize, [Validators.required])); this.cdRef.markForCheck(); }); } @@ -58,6 +65,7 @@ export class ManageMediaSettingsComponent implements OnInit { resetForm() { this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs); this.settingsForm.get('bookmarksDirectory')?.setValue(this.serverSettings.bookmarksDirectory); + this.settingsForm.get('coverImageSize')?.setValue(this.serverSettings.coverImageSize); this.settingsForm.markAsPristine(); this.cdRef.markForCheck(); } @@ -66,6 +74,7 @@ export class ManageMediaSettingsComponent implements OnInit { const modelSettings = Object.assign({}, this.serverSettings); modelSettings.encodeMediaAs = parseInt(this.settingsForm.get('encodeMediaAs')?.value, 10); modelSettings.bookmarksDirectory = this.settingsForm.get('bookmarksDirectory')?.value; + modelSettings.coverImageSize = parseInt(this.settingsForm.get('coverImageSize')?.value, 10); this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe(async (settings: ServerSettings) => { this.serverSettings = settings; diff --git a/UI/Web/src/app/admin/manage-system/manage-system.component.ts b/UI/Web/src/app/admin/manage-system/manage-system.component.ts index db02a9fb41..4e6467ce98 100644 --- a/UI/Web/src/app/admin/manage-system/manage-system.component.ts +++ b/UI/Web/src/app/admin/manage-system/manage-system.component.ts @@ -1,67 +1,30 @@ -import { Component, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ToastrService } from 'ngx-toastr'; -import { take } from 'rxjs/operators'; -import { ServerService } from 'src/app/_services/server.service'; -import { SettingsService } from '../settings.service'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core'; +import {ServerService} from 'src/app/_services/server.service'; import {ServerInfoSlim} from '../_models/server-info'; -import { ServerSettings } from '../_models/server-settings'; -import { NgIf } from '@angular/common'; -import {translate, TranslocoDirective} from "@ngneat/transloco"; +import {NgIf} from '@angular/common'; +import {TranslocoDirective} from "@ngneat/transloco"; @Component({ selector: 'app-manage-system', templateUrl: './manage-system.component.html', styleUrls: ['./manage-system.component.scss'], standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgIf, TranslocoDirective] }) export class ManageSystemComponent implements OnInit { - settingsForm: FormGroup = new FormGroup({}); - serverSettings!: ServerSettings; serverInfo!: ServerInfoSlim; + private readonly cdRef = inject(ChangeDetectorRef); - constructor(private settingsService: SettingsService, private toastr: ToastrService, - private serverService: ServerService) { } + constructor(public serverService: ServerService) { } ngOnInit(): void { - this.serverService.getServerInfo().pipe(take(1)).subscribe(info => { + this.serverService.getServerInfo().subscribe(info => { this.serverInfo = info; - }); - - this.settingsService.getServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => { - this.serverSettings = settings; - this.settingsForm.addControl('cacheDirectory', new FormControl(this.serverSettings.cacheDirectory, [Validators.required])); - this.settingsForm.addControl('taskScan', new FormControl(this.serverSettings.taskScan, [Validators.required])); - this.settingsForm.addControl('taskBackup', new FormControl(this.serverSettings.taskBackup, [Validators.required])); - this.settingsForm.addControl('port', new FormControl(this.serverSettings.port, [Validators.required])); - this.settingsForm.addControl('loggingLevel', new FormControl(this.serverSettings.loggingLevel, [Validators.required])); - this.settingsForm.addControl('allowStatCollection', new FormControl(this.serverSettings.allowStatCollection, [Validators.required])); - }); - } - - resetForm() { - this.settingsForm.get('cacheDirectory')?.setValue(this.serverSettings.cacheDirectory); - this.settingsForm.get('scanTask')?.setValue(this.serverSettings.taskScan); - this.settingsForm.get('taskBackup')?.setValue(this.serverSettings.taskBackup); - this.settingsForm.get('port')?.setValue(this.serverSettings.port); - this.settingsForm.get('loggingLevel')?.setValue(this.serverSettings.loggingLevel); - this.settingsForm.get('allowStatCollection')?.setValue(this.serverSettings.allowStatCollection); - this.settingsForm.markAsPristine(); - } - - saveSettings() { - const modelSettings = this.settingsForm.value; - - this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => { - this.serverSettings = settings; - this.resetForm(); - this.toastr.success(translate('toasts.server-settings-updated')); - }, (err: any) => { - console.error('error: ', err); + this.cdRef.markForCheck(); }); } } diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.html b/UI/Web/src/app/admin/manage-users/manage-users.component.html index 5edd2ab2ed..e6d452c56e 100644 --- a/UI/Web/src/app/admin/manage-users/manage-users.component.html +++ b/UI/Web/src/app/admin/manage-users/manage-users.component.html @@ -28,7 +28,7 @@

    + placement="top" [ngbTooltip]="t('resend-invite-tooltip')" [attr.aria-label]="t('resend-invite-alt', {user: member.username | titlecase})">{{t('resend')}} +

    + +
    +
    + +
    + +
    +
    +
    + +
    + + + + +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-builder/metadata-builder.component.scss b/UI/Web/src/app/metadata-filter/_components/metadata-builder/metadata-builder.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-builder/metadata-builder.component.ts b/UI/Web/src/app/metadata-filter/_components/metadata-builder/metadata-builder.component.ts new file mode 100644 index 0000000000..0426fdcd66 --- /dev/null +++ b/UI/Web/src/app/metadata-filter/_components/metadata-builder/metadata-builder.component.ts @@ -0,0 +1,93 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, DestroyRef, + EventEmitter, + inject, + Input, + OnInit, + Output +} from '@angular/core'; +import {MetadataService} from 'src/app/_services/metadata.service'; +import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service'; +import {SeriesFilterV2} from 'src/app/_models/metadata/v2/series-filter-v2'; +import {NgForOf, NgIf, UpperCasePipe} from "@angular/common"; +import {MetadataFilterRowComponent} from "../metadata-filter-row/metadata-filter-row.component"; +import {FilterStatement} from "../../../_models/metadata/v2/filter-statement"; +import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component"; +import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; +import {FilterCombination} from "../../../_models/metadata/v2/filter-combination"; +import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service"; +import {allFields} from "../../../_models/metadata/v2/filter-field"; +import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; +import {distinctUntilChanged, tap} from "rxjs/operators"; +import {translate, TranslocoDirective} from "@ngneat/transloco"; + +@Component({ + selector: 'app-metadata-builder', + templateUrl: './metadata-builder.component.html', + styleUrls: ['./metadata-builder.component.scss'], + standalone: true, + imports: [ + NgIf, + MetadataFilterRowComponent, + NgForOf, + CardActionablesComponent, + FormsModule, + NgbTooltip, + UpperCasePipe, + ReactiveFormsModule, + TranslocoDirective + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MetadataBuilderComponent implements OnInit { + + @Input({required: true}) filter!: SeriesFilterV2; + /** + * The number of statements that can be. 0 means unlimited. -1 means none. + */ + @Input() statementLimit = 0; + @Input() availableFilterFields = allFields; + @Output() update: EventEmitter = new EventEmitter(); + @Output() apply: EventEmitter = new EventEmitter(); + + private readonly cdRef = inject(ChangeDetectorRef); + private readonly metadataService = inject(MetadataService); + protected readonly utilityService = inject(UtilityService); + protected readonly filterUtilityService = inject(FilterUtilitiesService); + private readonly destroyRef = inject(DestroyRef); + protected readonly Breakpoint = Breakpoint; + + formGroup: FormGroup = new FormGroup({}); + + groupOptions: Array<{value: FilterCombination, title: string}> = [ + {value: FilterCombination.Or, title: translate('metadata-builder.or')}, + {value: FilterCombination.And, title: translate('metadata-builder.and')}, + ]; + + ngOnInit() { + this.formGroup.addControl('comparison', new FormControl(this.filter?.combination || FilterCombination.Or, [])); + this.formGroup.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef), tap(values => { + this.filter.combination = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterCombination; + this.update.emit(this.filter); + })).subscribe(); + } + + addFilter() { + this.filter.statements = [this.metadataService.createDefaultFilterStatement(), ...this.filter.statements]; + this.cdRef.markForCheck(); + } + + removeFilter(index: number) { + this.filter.statements = this.filter.statements.slice(0, index).concat(this.filter.statements.slice(index + 1)) + this.cdRef.markForCheck(); + } + + updateFilter(index: number, filterStmt: FilterStatement) { + this.metadataService.updateFilter(this.filter.statements, index, filterStmt); + this.update.emit(this.filter); + } + +} diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html new file mode 100644 index 0000000000..36fa209f0f --- /dev/null +++ b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.html @@ -0,0 +1,44 @@ + +
    +
    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + +
    + + +
    +
    diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.scss b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.scss new file mode 100644 index 0000000000..ee0c6d2788 --- /dev/null +++ b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.scss @@ -0,0 +1,3 @@ +::ng-deep .select2-selection__rendered { + padding-top: 4px !important; +} diff --git a/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts new file mode 100644 index 0000000000..fc6f0f84c6 --- /dev/null +++ b/UI/Web/src/app/metadata-filter/_components/metadata-filter-row/metadata-filter-row.component.ts @@ -0,0 +1,288 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef, + EventEmitter, + inject, + Input, + OnInit, + Output, +} from '@angular/core'; +import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; +import {FilterStatement} from '../../../_models/metadata/v2/filter-statement'; +import {BehaviorSubject, distinctUntilChanged, filter, map, Observable, of, startWith, switchMap, tap} from 'rxjs'; +import {MetadataService} from 'src/app/_services/metadata.service'; +import {mangaFormatFilters} from 'src/app/_models/metadata/series-filter'; +import {PersonRole} from 'src/app/_models/metadata/person'; +import {LibraryService} from 'src/app/_services/library.service'; +import {CollectionTagService} from 'src/app/_services/collection-tag.service'; +import {FilterComparison} from 'src/app/_models/metadata/v2/filter-comparison'; +import {allFields, FilterField} from 'src/app/_models/metadata/v2/filter-field'; +import {AsyncPipe, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgTemplateOutlet} from "@angular/common"; +import {FilterFieldPipe} from "../../_pipes/filter-field.pipe"; +import {FilterComparisonPipe} from "../../_pipes/filter-comparison.pipe"; +import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; +import {Select2Module, Select2Option} from "ng-select2-component"; +import {TagBadgeComponent} from "../../../shared/tag-badge/tag-badge.component"; + +enum PredicateType { + Text = 1, + Number = 2, + Dropdown = 3, +} + +const StringFields = [FilterField.SeriesName, FilterField.Summary, FilterField.Path, FilterField.FilePath]; +const NumberFields = [FilterField.ReadTime, FilterField.ReleaseYear, FilterField.ReadProgress, FilterField.UserRating]; +const DropdownFields = [FilterField.PublicationStatus, FilterField.Languages, FilterField.AgeRating, + FilterField.Translators, FilterField.Characters, FilterField.Publisher, + FilterField.Editor, FilterField.CoverArtist, FilterField.Letterer, + FilterField.Colorist, FilterField.Inker, FilterField.Penciller, + FilterField.Writers, FilterField.Genres, FilterField.Libraries, + FilterField.Formats, FilterField.CollectionTags, FilterField.Tags +]; + +const DropdownFieldsWithoutMustContains = [ + FilterField.Libraries, FilterField.Formats, FilterField.AgeRating, FilterField.PublicationStatus +]; +const DropdownFieldsThatIncludeNumberComparisons = [ + FilterField.AgeRating +]; +const NumberFieldsThatIncludeDateComparisons = [ + FilterField.ReleaseYear +]; + +const StringComparisons = [FilterComparison.Equal, + FilterComparison.NotEqual, + FilterComparison.BeginsWith, + FilterComparison.EndsWith, + FilterComparison.Matches]; +const DateComparisons = [FilterComparison.IsBefore, FilterComparison.IsAfter, FilterComparison.IsInLast, FilterComparison.IsNotInLast]; +const NumberComparisons = [FilterComparison.Equal, + FilterComparison.NotEqual, + FilterComparison.LessThan, + FilterComparison.LessThanEqual, + FilterComparison.GreaterThan, + FilterComparison.GreaterThanEqual]; +const DropdownComparisons = [FilterComparison.Equal, + FilterComparison.NotEqual, + FilterComparison.Contains, + FilterComparison.NotContains, + FilterComparison.MustContains]; + +@Component({ + selector: 'app-metadata-row-filter', + templateUrl: './metadata-filter-row.component.html', + styleUrls: ['./metadata-filter-row.component.scss'], + standalone: true, + imports: [ + ReactiveFormsModule, + AsyncPipe, + FilterFieldPipe, + FilterComparisonPipe, + NgSwitch, + NgSwitchCase, + NgForOf, + NgIf, + Select2Module, + NgTemplateOutlet, + TagBadgeComponent + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MetadataFilterRowComponent implements OnInit { + + @Input() index: number = 0; // This is only for debugging + /** + * Slightly misleading as this is the initial state and will be updated on the filterStatement event emitter + */ + @Input() preset!: FilterStatement; + @Input() availableFields: Array = allFields; + @Output() filterStatement = new EventEmitter(); + + private readonly cdRef = inject(ChangeDetectorRef); + private readonly destroyRef = inject(DestroyRef); + + formGroup: FormGroup = new FormGroup({ + 'comparison': new FormControl(FilterComparison.Equal, []), + 'filterValue': new FormControl('', []), + }); + validComparisons$: BehaviorSubject = new BehaviorSubject([FilterComparison.Equal] as FilterComparison[]); + predicateType$: BehaviorSubject = new BehaviorSubject(PredicateType.Text as PredicateType); + dropdownOptions$ = of([]); + + loaded: boolean = false; + protected readonly PredicateType = PredicateType; + + get MultipleDropdownAllowed() { + const comp = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison; + return comp === FilterComparison.Contains || comp === FilterComparison.NotContains || comp === FilterComparison.MustContains; + } + + constructor(private readonly metadataService: MetadataService, private readonly libraryService: LibraryService, + private readonly collectionTagService: CollectionTagService) {} + + ngOnInit() { + this.formGroup.addControl('input', new FormControl(FilterField.SeriesName, [])); + + this.formGroup.get('input')?.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe((val: string) => this.handleFieldChange(val)); + this.populateFromPreset(); + + this.formGroup.get('filterValue')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(); + + // Dropdown dynamic option selection + this.dropdownOptions$ = this.formGroup.get('input')!.valueChanges.pipe( + startWith(this.preset.value), + distinctUntilChanged(), + filter(() => { + const inputVal = parseInt(this.formGroup.get('input')?.value, 10) as FilterField; + return DropdownFields.includes(inputVal); + }), + switchMap((_) => this.getDropdownObservable()), + takeUntilDestroyed(this.destroyRef) + ); + + + this.formGroup!.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => { + const stmt = { + comparison: parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison, + field: parseInt(this.formGroup.get('input')?.value, 10) as FilterField, + value: this.formGroup.get('filterValue')?.value! + }; + + // Some ids can get through and be numbers, convert them to strings for the backend + if (typeof stmt.value === 'number' && !Number.isNaN(stmt.value)) { + stmt.value = stmt.value + ''; + } + + if (!stmt.value && stmt.field !== FilterField.SeriesName) return; + this.filterStatement.emit(stmt); + }); + + this.loaded = true; + this.cdRef.markForCheck(); + } + + + + populateFromPreset() { + const val = this.preset.value === "undefined" || !this.preset.value ? '' : this.preset.value; + this.formGroup.get('comparison')?.patchValue(this.preset.comparison); + this.formGroup.get('input')?.patchValue(this.preset.field); + + if (StringFields.includes(this.preset.field)) { + this.formGroup.get('filterValue')?.patchValue(val); + } else if (DropdownFields.includes(this.preset.field)) { + if (this.MultipleDropdownAllowed || val.includes(',')) { + this.formGroup.get('filterValue')?.patchValue(val.split(',').map(d => parseInt(d, 10))); + } else { + if (this.preset.field === FilterField.Languages) { + this.formGroup.get('filterValue')?.patchValue(val); + } else { + this.formGroup.get('filterValue')?.patchValue(parseInt(val, 10)); + } + } + } else { + this.formGroup.get('filterValue')?.patchValue(parseInt(val, 10)); + } + + + this.cdRef.markForCheck(); + } + + getDropdownObservable(): Observable { + const filterField = parseInt(this.formGroup.get('input')?.value, 10) as FilterField; + switch (filterField) { + case FilterField.PublicationStatus: + return this.metadataService.getAllPublicationStatus().pipe(map(pubs => pubs.map(pub => { + return {value: pub.value, label: pub.title} + }))); + case FilterField.AgeRating: + return this.metadataService.getAllAgeRatings().pipe(map(ratings => ratings.map(rating => { + return {value: rating.value, label: rating.title} + }))); + case FilterField.Genres: + return this.metadataService.getAllGenres().pipe(map(genres => genres.map(genre => { + return {value: genre.id, label: genre.title} + }))); + case FilterField.Languages: + return this.metadataService.getAllLanguages().pipe(map(statuses => statuses.map(status => { + return {value: status.isoCode, label: status.title + ` (${status.isoCode})`} + }))); + case FilterField.Formats: + return of(mangaFormatFilters).pipe(map(statuses => statuses.map(status => { + return {value: status.value, label: status.title} + }))); + case FilterField.Libraries: + return this.libraryService.getLibraries().pipe(map(libs => libs.map(lib => { + return {value: lib.id, label: lib.name} + }))); + case FilterField.Tags: + return this.metadataService.getAllTags().pipe(map(statuses => statuses.map(status => { + return {value: status.id, label: status.title} + }))); + case FilterField.CollectionTags: + return this.collectionTagService.allTags().pipe(map(statuses => statuses.map(status => { + return {value: status.id, label: status.title} + }))); + case FilterField.Characters: return this.getPersonOptions(PersonRole.Character); + case FilterField.Colorist: return this.getPersonOptions(PersonRole.Colorist); + case FilterField.CoverArtist: return this.getPersonOptions(PersonRole.CoverArtist); + case FilterField.Editor: return this.getPersonOptions(PersonRole.Editor); + case FilterField.Inker: return this.getPersonOptions(PersonRole.Inker); + case FilterField.Letterer: return this.getPersonOptions(PersonRole.Letterer); + case FilterField.Penciller: return this.getPersonOptions(PersonRole.Penciller); + case FilterField.Publisher: return this.getPersonOptions(PersonRole.Publisher); + case FilterField.Translators: return this.getPersonOptions(PersonRole.Translator); + case FilterField.Writers: return this.getPersonOptions(PersonRole.Writer); + } + return of([]); + } + + getPersonOptions(role: PersonRole) { + return this.metadataService.getAllPeopleByRole(role).pipe(map(people => people.map(person => { + return {value: person.id, label: person.name} + }))); + } + + + handleFieldChange(val: string) { + const inputVal = parseInt(val, 10) as FilterField; + + if (StringFields.includes(inputVal)) { + this.validComparisons$.next(StringComparisons); + this.predicateType$.next(PredicateType.Text); + + if (this.loaded) { + this.formGroup.get('filterValue')?.patchValue(''); + } + return; + } + + if (NumberFields.includes(inputVal)) { + const comps = [...NumberComparisons]; + if (NumberFieldsThatIncludeDateComparisons.includes(inputVal)) { + comps.push(...DateComparisons); + } + this.validComparisons$.next(comps); + this.predicateType$.next(PredicateType.Number); + if (this.loaded) this.formGroup.get('filterValue')?.patchValue(0); + return; + } + + if (DropdownFields.includes(inputVal)) { + let comps = [...DropdownComparisons]; + if (DropdownFieldsThatIncludeNumberComparisons.includes(inputVal)) { + comps.push(...NumberComparisons); + } + if (DropdownFieldsWithoutMustContains.includes(inputVal)) { + comps = comps.filter(c => c !== FilterComparison.MustContains); + } + this.validComparisons$.next(comps); + this.predicateType$.next(PredicateType.Dropdown); + if (this.loaded) this.formGroup.get('filterValue')?.patchValue(0); + return; + } + } + +} diff --git a/UI/Web/src/app/metadata-filter/_pipes/filter-comparison.pipe.ts b/UI/Web/src/app/metadata-filter/_pipes/filter-comparison.pipe.ts new file mode 100644 index 0000000000..33a7c960e4 --- /dev/null +++ b/UI/Web/src/app/metadata-filter/_pipes/filter-comparison.pipe.ts @@ -0,0 +1,50 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { FilterComparison } from 'src/app/_models/metadata/v2/filter-comparison'; +import {translate} from "@ngneat/transloco"; + +@Pipe({ + name: 'filterComparison', + standalone: true +}) +export class FilterComparisonPipe implements PipeTransform { + + transform(value: FilterComparison): string { + switch (value) { + case FilterComparison.BeginsWith: + return translate('filter-comparison-pipe.begins-with'); + case FilterComparison.Contains: + return translate('filter-comparison-pipe.contains'); + case FilterComparison.Equal: + return translate('filter-comparison-pipe.equal'); + case FilterComparison.GreaterThan: + return translate('filter-comparison-pipe.greater-than'); + case FilterComparison.GreaterThanEqual: + return translate('filter-comparison-pipe.greater-than-or-equal'); + case FilterComparison.LessThan: + return translate('filter-comparison-pipe.less-than'); + case FilterComparison.LessThanEqual: + return translate('filter-comparison-pipe.less-than-or-equal'); + case FilterComparison.Matches: + return translate('filter-comparison-pipe.matches'); + case FilterComparison.NotContains: + return translate('filter-comparison-pipe.does-not-contain'); + case FilterComparison.NotEqual: + return translate('filter-comparison-pipe.not-equal'); + case FilterComparison.EndsWith: + return translate('filter-comparison-pipe.ends-with'); + case FilterComparison.IsBefore: + return translate('filter-comparison-pipe.is-before'); + case FilterComparison.IsAfter: + return translate('filter-comparison-pipe.is-after'); + case FilterComparison.IsInLast: + return translate('filter-comparison-pipe.is-in-last'); + case FilterComparison.IsNotInLast: + return translate('filter-comparison-pipe.is-not-in-last'); + case FilterComparison.MustContains: + return translate('filter-comparison-pipe.must-contains'); + default: + throw new Error(`Invalid FilterComparison value: ${value}`); + } + } + +} diff --git a/UI/Web/src/app/metadata-filter/_pipes/filter-field.pipe.ts b/UI/Web/src/app/metadata-filter/_pipes/filter-field.pipe.ts new file mode 100644 index 0000000000..d8e67b04d7 --- /dev/null +++ b/UI/Web/src/app/metadata-filter/_pipes/filter-field.pipe.ts @@ -0,0 +1,70 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { FilterField } from 'src/app/_models/metadata/v2/filter-field'; +import {translate} from "@ngneat/transloco"; + +@Pipe({ + name: 'filterField', + standalone: true +}) +export class FilterFieldPipe implements PipeTransform { + + transform(value: FilterField): string { + switch (value) { + case FilterField.AgeRating: + return translate('filter-field-pipe.age-rating'); + case FilterField.Characters: + return translate('filter-field-pipe.characters'); + case FilterField.CollectionTags: + return translate('filter-field-pipe.collection-tags'); + case FilterField.Colorist: + return translate('filter-field-pipe.colorist'); + case FilterField.CoverArtist: + return translate('filter-field-pipe.cover-artist'); + case FilterField.Editor: + return translate('filter-field-pipe.editor'); + case FilterField.Formats: + return translate('filter-field-pipe.formats'); + case FilterField.Genres: + return translate('filter-field-pipe.genres'); + case FilterField.Inker: + return translate('filter-field-pipe.inker'); + case FilterField.Languages: + return translate('filter-field-pipe.languages'); + case FilterField.Libraries: + return translate('filter-field-pipe.libraries'); + case FilterField.Letterer: + return translate('filter-field-pipe.letterer'); + case FilterField.PublicationStatus: + return translate('filter-field-pipe.publication-status'); + case FilterField.Penciller: + return translate('filter-field-pipe.penciller'); + case FilterField.Publisher: + return translate('filter-field-pipe.publisher'); + case FilterField.ReadProgress: + return translate('filter-field-pipe.read-progress'); + case FilterField.ReadTime: + return translate('filter-field-pipe.read-time'); + case FilterField.ReleaseYear: + return translate('filter-field-pipe.release-year'); + case FilterField.SeriesName: + return translate('filter-field-pipe.series-name'); + case FilterField.Summary: + return translate('filter-field-pipe.summary'); + case FilterField.Tags: + return translate('filter-field-pipe.tags'); + case FilterField.Translators: + return translate('filter-field-pipe.translators'); + case FilterField.UserRating: + return translate('filter-field-pipe.user-rating'); + case FilterField.Writers: + return translate('filter-field-pipe.writers'); + case FilterField.Path: + return translate('filter-field-pipe.path'); + case FilterField.FilePath: + return translate('filter-field-pipe.file-path'); + default: + throw new Error(`Invalid FilterField value: ${value}`); + } + } + +} diff --git a/UI/Web/src/app/metadata-filter/filter-settings.ts b/UI/Web/src/app/metadata-filter/filter-settings.ts index c80ed96ac5..ce641b8c54 100644 --- a/UI/Web/src/app/metadata-filter/filter-settings.ts +++ b/UI/Web/src/app/metadata-filter/filter-settings.ts @@ -1,24 +1,10 @@ -import { SeriesFilter } from "../_models/metadata/series-filter"; +import { SeriesFilterV2 } from "../_models/metadata/v2/series-filter-v2"; export class FilterSettings { - libraryDisabled = false; - formatDisabled = false; - collectionDisabled = false; - genresDisabled = false; - peopleDisabled = false; - readProgressDisabled = false; - ratingDisabled = false; sortDisabled = false; - ageRatingDisabled = false; - tagsDisabled = false; - languageDisabled = false; - publicationStatusDisabled = false; - searchNameDisabled = false; - releaseYearDisabled = false; - presets: SeriesFilter | undefined; + presetsV2: SeriesFilterV2 | undefined; /** - * Should the filter section be open by default - * @deprecated This is deprecated UX pattern. New style is to show highlight on filter button. + * The number of statements that can be on the filter. Set to 1 to disable adding more. */ - openByDefault = false; - } \ No newline at end of file + statementLimit: number = 0; + } diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.html b/UI/Web/src/app/metadata-filter/metadata-filter.component.html index 6df87f1f4f..95628e33e1 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.html +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.html @@ -1,17 +1,18 @@ -
    +
    -
    +
    {{t('filter-title')}}
    +
    @@ -19,357 +20,51 @@
    - {{t('format-tooltip')}} -
    +
    -
    -
    - - - - - {{item.title}} - - - {{item.title}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - - {{item.title}} - - - {{item.title}} - - -
    -
    - -
    -
    - - - - {{item.title}} - - - {{item.title}} - - -
    -
    - -
    -
    - - - - {{item.title}} - - - {{item.title}} - - -
    -
    -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    - -
    -
    - - - - {{item.name}} - - - {{item.name}} - - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    - -
    - - - - - -
    -
    - -
    - - - - {{item.title}} - - - {{item.title}} - - -
    - -
    - - - - {{item.title}} - - - {{item.title}} - - -
    - -
    - - - - {{item.title}} - - - {{item.title}} - - -
    -
    + +
    -
    -
    -
    -
    - - - {{t('series-name-tooltip')}} - -
    -
    -
    -
    -
    -
    - - + +
    +
    +
    + +
    -
    - -
    -
    - - -
    - -
    -
    -
    -
    +
    +
    - -
    -
    -
    -
    - +
    +
    -
    - +
    +
    -
    + +
    + + + +
    + +
    +
    +
    - + + diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts index 324dbdeb97..713d07877d 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts @@ -2,40 +2,31 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ContentChild, DestroyRef, + ContentChild, + DestroyRef, EventEmitter, inject, Input, OnInit, Output } from '@angular/core'; -import { FormControl, FormGroup, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms'; -import { NgbCollapse, NgbTooltip, NgbRating } from '@ng-bootstrap/ng-bootstrap'; -import { distinctUntilChanged, forkJoin, map, Observable, of, ReplaySubject } from 'rxjs'; -import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service'; -import { Breakpoint, UtilityService } from '../shared/_services/utility.service'; -import { TypeaheadSettings } from '../typeahead/_models/typeahead-settings'; -import { CollectionTag } from '../_models/collection-tag'; -import { Genre } from '../_models/metadata/genre'; -import { Library } from '../_models/library'; -import { MangaFormat } from '../_models/manga-format'; -import { AgeRatingDto } from '../_models/metadata/age-rating-dto'; -import { Language } from '../_models/metadata/language'; -import { PublicationStatusDto } from '../_models/metadata/publication-status-dto'; -import { Person, PersonRole } from '../_models/metadata/person'; -import { FilterEvent, FilterItem, mangaFormatFilters, SeriesFilter, SortField } from '../_models/metadata/series-filter'; -import { Tag } from '../_models/tag'; -import { CollectionTagService } from '../_services/collection-tag.service'; -import { LibraryService } from '../_services/library.service'; -import { MetadataService } from '../_services/metadata.service'; -import { ToggleService } from '../_services/toggle.service'; -import { FilterSettings } from './filter-settings'; +import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {NgbCollapse, NgbRating, NgbTooltip} from '@ng-bootstrap/ng-bootstrap'; +import {FilterUtilitiesService} from '../shared/_services/filter-utilities.service'; +import {Breakpoint, UtilityService} from '../shared/_services/utility.service'; +import {Library} from '../_models/library'; +import {allSortFields, FilterEvent, FilterItem, SortField} from '../_models/metadata/series-filter'; +import {ToggleService} from '../_services/toggle.service'; +import {FilterSettings} from './filter-settings'; +import {SeriesFilterV2} from '../_models/metadata/v2/series-filter-v2'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import { TypeaheadComponent } from '../typeahead/_components/typeahead.component'; -import { DrawerComponent } from '../shared/drawer/drawer.component'; -import { NgIf, NgTemplateOutlet, AsyncPipe } from '@angular/common'; +import {TypeaheadComponent} from '../typeahead/_components/typeahead.component'; +import {DrawerComponent} from '../shared/drawer/drawer.component'; +import {AsyncPipe, NgForOf, NgIf, NgTemplateOutlet} from '@angular/common'; import {TranslocoModule} from "@ngneat/transloco"; import {SortFieldPipe} from "../pipe/sort-field.pipe"; +import {MetadataBuilderComponent} from "./_components/metadata-builder/metadata-builder.component"; +import {allFields} from "../_models/metadata/v2/filter-field"; @Component({ selector: 'app-metadata-filter', @@ -44,7 +35,7 @@ import {SortFieldPipe} from "../pipe/sort-field.pipe"; changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [NgIf, NgbCollapse, NgTemplateOutlet, DrawerComponent, NgbTooltip, TypeaheadComponent, - ReactiveFormsModule, FormsModule, NgbRating, AsyncPipe, TranslocoModule, SortFieldPipe] + ReactiveFormsModule, FormsModule, NgbRating, AsyncPipe, TranslocoModule, SortFieldPipe, MetadataBuilderComponent, NgForOf] }) export class MetadataFilterComponent implements OnInit { @@ -64,50 +55,35 @@ export class MetadataFilterComponent implements OnInit { @ContentChild('[ngbCollapse]') collapse!: NgbCollapse; private readonly destroyRef = inject(DestroyRef); + public readonly utilityService = inject(UtilityService); - formatSettings: TypeaheadSettings> = new TypeaheadSettings(); - librarySettings: TypeaheadSettings = new TypeaheadSettings(); - genreSettings: TypeaheadSettings = new TypeaheadSettings(); - collectionSettings: TypeaheadSettings = new TypeaheadSettings(); - ageRatingSettings: TypeaheadSettings = new TypeaheadSettings(); - publicationStatusSettings: TypeaheadSettings = new TypeaheadSettings(); - tagsSettings: TypeaheadSettings = new TypeaheadSettings(); - languageSettings: TypeaheadSettings = new TypeaheadSettings(); - peopleSettings: {[PersonRole: string]: TypeaheadSettings} = {}; - resetTypeaheads: ReplaySubject = new ReplaySubject(1); - /** * Controls the visibility of extended controls that sit below the main header. */ filteringCollapsed: boolean = true; - filter!: SeriesFilter; libraries: Array> = []; - - readProgressGroup!: FormGroup; sortGroup!: FormGroup; - seriesNameGroup!: FormGroup; - releaseYearRange!: FormGroup; isAscendingSort: boolean = true; updateApplied: number = 0; fullyLoaded: boolean = false; + filterV2: SeriesFilterV2 | undefined; + allSortFields = allSortFields; + allFilterFields = allFields; - get PersonRole(): typeof PersonRole { - return PersonRole; + handleFilters(filter: SeriesFilterV2) { + this.filterV2 = filter; } - get SortField(): typeof SortField { - return SortField; - } - constructor(private libraryService: LibraryService, private metadataService: MetadataService, private utilityService: UtilityService, - private collectionTagService: CollectionTagService, public toggleService: ToggleService, - private readonly cdRef: ChangeDetectorRef, private filterUtilitySerivce: FilterUtilitiesService) { - } + private readonly cdRef = inject(ChangeDetectorRef); + + + constructor(public toggleService: ToggleService) {} ngOnInit(): void { if (this.filterSettings === undefined) { @@ -123,78 +99,6 @@ export class MetadataFilterComponent implements OnInit { }); } - this.filter = this.filterUtilitySerivce.createSeriesFilter(); - this.readProgressGroup = new FormGroup({ - read: new FormControl({value: this.filter.readStatus.read, disabled: this.filterSettings.readProgressDisabled}, []), - notRead: new FormControl({value: this.filter.readStatus.notRead, disabled: this.filterSettings.readProgressDisabled}, []), - inProgress: new FormControl({value: this.filter.readStatus.inProgress, disabled: this.filterSettings.readProgressDisabled}, []), - }); - - this.sortGroup = new FormGroup({ - sortField: new FormControl({value: this.filter.sortOptions?.sortField || SortField.SortName, disabled: this.filterSettings.sortDisabled}, []), - }); - - this.seriesNameGroup = new FormGroup({ - seriesNameQuery: new FormControl({value: this.filter.seriesNameQuery || '', disabled: this.filterSettings.searchNameDisabled}, []) - }); - - this.releaseYearRange = new FormGroup({ - min: new FormControl({value: undefined, disabled: this.filterSettings.releaseYearDisabled}, [Validators.min(1000), Validators.max(9999), Validators.maxLength(4), Validators.minLength(4)]), - max: new FormControl({value: undefined, disabled: this.filterSettings.releaseYearDisabled}, [Validators.min(1000), Validators.max(9999), Validators.maxLength(4), Validators.minLength(4)]) - }); - - this.readProgressGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(changes => { - this.filter.readStatus.read = this.readProgressGroup.get('read')?.value; - this.filter.readStatus.inProgress = this.readProgressGroup.get('inProgress')?.value; - this.filter.readStatus.notRead = this.readProgressGroup.get('notRead')?.value; - - let sum = 0; - sum += (this.filter.readStatus.read ? 1 : 0); - sum += (this.filter.readStatus.inProgress ? 1 : 0); - sum += (this.filter.readStatus.notRead ? 1 : 0); - - if (sum === 1) { - if (this.filter.readStatus.read) this.readProgressGroup.get('read')?.disable({ emitEvent: false }); - if (this.filter.readStatus.notRead) this.readProgressGroup.get('notRead')?.disable({ emitEvent: false }); - if (this.filter.readStatus.inProgress) this.readProgressGroup.get('inProgress')?.disable({ emitEvent: false }); - } else { - this.readProgressGroup.get('read')?.enable({ emitEvent: false }); - this.readProgressGroup.get('notRead')?.enable({ emitEvent: false }); - this.readProgressGroup.get('inProgress')?.enable({ emitEvent: false }); - } - this.cdRef.markForCheck(); - }); - - this.sortGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(changes => { - if (this.filter.sortOptions == null) { - this.filter.sortOptions = { - isAscending: this.isAscendingSort, - sortField: parseInt(this.sortGroup.get('sortField')?.value, 10) - }; - } - this.filter.sortOptions.sortField = parseInt(this.sortGroup.get('sortField')?.value, 10); - this.cdRef.markForCheck(); - }); - - this.seriesNameGroup.get('seriesNameQuery')?.valueChanges.pipe( - map(val => (val || '').trim()), - distinctUntilChanged(), - takeUntilDestroyed(this.destroyRef) - ) - .subscribe(changes => { - this.filter.seriesNameQuery = changes; // TODO: See if we can make this into observable - this.cdRef.markForCheck(); - }); - - this.releaseYearRange.valueChanges.pipe( - distinctUntilChanged(), - takeUntilDestroyed(this.destroyRef) - ) - .subscribe(changes => { - this.filter.releaseYearRange = {min: this.releaseYearRange.get('min')?.value, max: this.releaseYearRange.get('max')?.value}; - this.cdRef.markForCheck(); - }); - this.loadFromPresetsAndSetup(); } @@ -205,444 +109,78 @@ export class MetadataFilterComponent implements OnInit { this.cdRef.markForCheck(); } - getPersonsSettings(role: PersonRole) { - return this.peopleSettings[role]; - } - - loadFromPresetsAndSetup() { - this.fullyLoaded = false; - if (this.filterSettings.presets) { - this.readProgressGroup.get('read')?.patchValue(this.filterSettings.presets.readStatus.read); - this.readProgressGroup.get('notRead')?.patchValue(this.filterSettings.presets.readStatus.notRead); - this.readProgressGroup.get('inProgress')?.patchValue(this.filterSettings.presets.readStatus.inProgress); - - if (this.filterSettings.presets.sortOptions) { - this.sortGroup.get('sortField')?.setValue(this.filterSettings.presets.sortOptions.sortField); - this.isAscendingSort = this.filterSettings.presets.sortOptions.isAscending; - if (this.filter.sortOptions) { - this.filter.sortOptions.isAscending = this.isAscendingSort; - this.filter.sortOptions.sortField = this.filterSettings.presets.sortOptions.sortField; - } - } - - if (this.filterSettings.presets.rating > 0) { - this.updateRating(this.filterSettings.presets.rating); - } - - if (this.filterSettings.presets.seriesNameQuery !== '') { - this.seriesNameGroup.get('searchNameQuery')?.setValue(this.filterSettings.presets.seriesNameQuery); - } - } - - this.setupFormatTypeahead(); - this.cdRef.markForCheck(); - - forkJoin([ - this.setupLibraryTypeahead(), - this.setupCollectionTagTypeahead(), - this.setupAgeRatingSettings(), - this.setupPublicationStatusSettings(), - this.setupTagSettings(), - this.setupLanguageSettings(), - this.setupGenreTypeahead(), - this.setupPersonTypeahead(), - ]).subscribe(results => { - this.fullyLoaded = true; - this.resetTypeaheads.next(false); // Pass false to ensure we reset to the preset and not to an empty typeahead - this.cdRef.markForCheck(); - this.apply(); - }); - } - - setupFormatTypeahead() { - this.formatSettings.minCharacters = 0; - this.formatSettings.multiple = true; - this.formatSettings.id = 'format'; - this.formatSettings.unique = true; - this.formatSettings.addIfNonExisting = false; - this.formatSettings.fetchFn = (filter: string) => of(mangaFormatFilters).pipe(map(items => this.formatSettings.compareFn(items, filter))); - this.formatSettings.compareFn = (options: FilterItem[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); - } - - this.formatSettings.selectionCompareFn = (a: FilterItem, b: FilterItem) => { - return a.title == b.title; - } - - if (this.filterSettings.presets?.formats && this.filterSettings.presets?.formats.length > 0) { - this.formatSettings.savedData = mangaFormatFilters.filter(item => this.filterSettings.presets?.formats.includes(item.value)); - this.updateFormatFilters(this.formatSettings.savedData); - } - } - - setupLibraryTypeahead() { - this.librarySettings.minCharacters = 0; - this.librarySettings.multiple = true; - this.librarySettings.id = 'libraries'; - this.librarySettings.unique = true; - this.librarySettings.addIfNonExisting = false; - this.librarySettings.fetchFn = (filter: string) => { - return this.libraryService.getLibraries() - .pipe(map(items => this.librarySettings.compareFn(items, filter))); - }; - this.librarySettings.compareFn = (options: Library[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.name, filter)); - } - this.librarySettings.selectionCompareFn = (a: Library, b: Library) => { - return a.name == b.name; - } - - if (this.filterSettings.presets?.libraries && this.filterSettings.presets?.libraries.length > 0) { - return this.librarySettings.fetchFn('').pipe(map(libraries => { - this.librarySettings.savedData = libraries.filter(item => this.filterSettings.presets?.libraries.includes(item.id)); - this.updateLibraryFilters(this.librarySettings.savedData); - return of(true); - })); - } - return of(true); - } - - setupGenreTypeahead() { - this.genreSettings.minCharacters = 0; - this.genreSettings.multiple = true; - this.genreSettings.id = 'genres'; - this.genreSettings.unique = true; - this.genreSettings.addIfNonExisting = false; - this.genreSettings.fetchFn = (filter: string) => { - return this.metadataService.getAllGenres(this.filter.libraries) - .pipe(map(items => this.genreSettings.compareFn(items, filter))); - }; - this.genreSettings.compareFn = (options: Genre[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); - } - this.genreSettings.selectionCompareFn = (a: Genre, b: Genre) => { - return a.title == b.title; - } - - if (this.filterSettings.presets?.genres && this.filterSettings.presets?.genres.length > 0) { - return this.genreSettings.fetchFn('').pipe(map(genres => { - this.genreSettings.savedData = genres.filter(item => this.filterSettings.presets?.genres.includes(item.id)); - this.updateGenreFilters(this.genreSettings.savedData); - return of(true); - })); - } - return of(true); - } - - setupAgeRatingSettings() { - this.ageRatingSettings.minCharacters = 0; - this.ageRatingSettings.multiple = true; - this.ageRatingSettings.id = 'age-rating'; - this.ageRatingSettings.unique = true; - this.ageRatingSettings.addIfNonExisting = false; - this.ageRatingSettings.fetchFn = (filter: string) => this.metadataService.getAllAgeRatings(this.filter.libraries) - .pipe(map(items => this.ageRatingSettings.compareFn(items, filter))); - - this.ageRatingSettings.compareFn = (options: AgeRatingDto[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); - } - - - this.ageRatingSettings.selectionCompareFn = (a: AgeRatingDto, b: AgeRatingDto) => { - return a.title == b.title; - } - - if (this.filterSettings.presets?.ageRating && this.filterSettings.presets?.ageRating.length > 0) { - return this.ageRatingSettings.fetchFn('').pipe(map(rating => { - this.ageRatingSettings.savedData = rating.filter(item => this.filterSettings.presets?.ageRating.includes(item.value)); - this.updateAgeRating(this.ageRatingSettings.savedData); - return of(true); - })); - } - return of(true); - } - - setupPublicationStatusSettings() { - this.publicationStatusSettings.minCharacters = 0; - this.publicationStatusSettings.multiple = true; - this.publicationStatusSettings.id = 'publication-status'; - this.publicationStatusSettings.unique = true; - this.publicationStatusSettings.addIfNonExisting = false; - this.publicationStatusSettings.fetchFn = (filter: string) => this.metadataService.getAllPublicationStatus(this.filter.libraries) - .pipe(map(items => this.publicationStatusSettings.compareFn(items, filter))); - - this.publicationStatusSettings.compareFn = (options: PublicationStatusDto[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); - } - - this.publicationStatusSettings.selectionCompareFn = (a: PublicationStatusDto, b: PublicationStatusDto) => { - return a.title == b.title; - } - - if (this.filterSettings.presets?.publicationStatus && this.filterSettings.presets?.publicationStatus.length > 0) { - return this.publicationStatusSettings.fetchFn('').pipe(map(statuses => { - this.publicationStatusSettings.savedData = statuses.filter(item => this.filterSettings.presets?.publicationStatus.includes(item.value)); - this.updatePublicationStatus(this.publicationStatusSettings.savedData); - return of(true); - })); - } - return of(true); - } - - setupTagSettings() { - this.tagsSettings.minCharacters = 0; - this.tagsSettings.multiple = true; - this.tagsSettings.id = 'tags'; - this.tagsSettings.unique = true; - this.tagsSettings.addIfNonExisting = false; - this.tagsSettings.compareFn = (options: Tag[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); + deepClone(obj: any): any { + if (obj === null || typeof obj !== 'object') { + return obj; } - this.tagsSettings.fetchFn = (filter: string) => this.metadataService.getAllTags(this.filter.libraries) - .pipe(map(items => this.tagsSettings.compareFn(items, filter))); - this.tagsSettings.selectionCompareFn = (a: Tag, b: Tag) => { - return a.id == b.id; + if (obj instanceof Array) { + return obj.map(item => this.deepClone(item)); } - if (this.filterSettings.presets?.tags && this.filterSettings.presets?.tags.length > 0) { - return this.tagsSettings.fetchFn('').pipe(map(tags => { - this.tagsSettings.savedData = tags.filter(item => this.filterSettings.presets?.tags.includes(item.id)); - this.updateTagFilters(this.tagsSettings.savedData); - return of(true); - })); - } - return of(true); - } - - setupLanguageSettings() { - this.languageSettings.minCharacters = 0; - this.languageSettings.multiple = true; - this.languageSettings.id = 'languages'; - this.languageSettings.unique = true; - this.languageSettings.addIfNonExisting = false; - this.languageSettings.compareFn = (options: Language[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); - } - this.languageSettings.fetchFn = (filter: string) => this.metadataService.getAllLanguages(this.filter.libraries) - .pipe(map(items => this.languageSettings.compareFn(items, filter))); - - this.languageSettings.selectionCompareFn = (a: Language, b: Language) => { - return a.isoCode == b.isoCode; - } - - if (this.filterSettings.presets?.languages && this.filterSettings.presets?.languages.length > 0) { - return this.languageSettings.fetchFn('').pipe(map(languages => { - this.languageSettings.savedData = languages.filter(item => this.filterSettings.presets?.languages.includes(item.isoCode)); - this.updateLanguages(this.languageSettings.savedData); - return of(true); - })); - } - return of(true); - } - - setupCollectionTagTypeahead() { - this.collectionSettings.minCharacters = 0; - this.collectionSettings.multiple = true; - this.collectionSettings.id = 'collections'; - this.collectionSettings.unique = true; - this.collectionSettings.addIfNonExisting = false; - this.collectionSettings.compareFn = (options: CollectionTag[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.title, filter)); - } - this.collectionSettings.fetchFn = (filter: string) => this.collectionTagService.allTags() - .pipe(map(items => this.collectionSettings.compareFn(items, filter))); - - this.collectionSettings.selectionCompareFn = (a: CollectionTag, b: CollectionTag) => { - return a.id == b.id; - } - - if (this.filterSettings.presets?.collectionTags && this.filterSettings.presets?.collectionTags.length > 0) { - return this.collectionSettings.fetchFn('').pipe(map(tags => { - this.collectionSettings.savedData = tags.filter(item => this.filterSettings.presets?.collectionTags.includes(item.id)); - this.updateCollectionFilters(this.collectionSettings.savedData); - return of(true); - })); - } - return of(true); - } - - updateFromPreset(id: string, peopleFilterField: Array, presetField: Array | undefined, role: PersonRole) { - const personSettings = this.createBlankPersonSettings(id, role) - if (presetField && presetField.length > 0) { - const fetch = personSettings.fetchFn as ((filter: string) => Observable); - return fetch('').pipe(map(people => { - personSettings.savedData = people.filter(item => presetField.includes(item.id)); - this.peopleSettings[role] = personSettings; - this.updatePersonFilters(personSettings.savedData, role); - return true; - })); - } - - this.peopleSettings[role] = personSettings; - return of(true); - - } - - setupPersonTypeahead() { - this.peopleSettings = {}; - - return forkJoin([ - this.updateFromPreset('writers', this.filter.writers, this.filterSettings.presets?.writers, PersonRole.Writer), - this.updateFromPreset('character', this.filter.character, this.filterSettings.presets?.character, PersonRole.Character), - this.updateFromPreset('colorist', this.filter.colorist, this.filterSettings.presets?.colorist, PersonRole.Colorist), - this.updateFromPreset('cover-artist', this.filter.coverArtist, this.filterSettings.presets?.coverArtist, PersonRole.CoverArtist), - this.updateFromPreset('editor', this.filter.editor, this.filterSettings.presets?.editor, PersonRole.Editor), - this.updateFromPreset('inker', this.filter.inker, this.filterSettings.presets?.inker, PersonRole.Inker), - this.updateFromPreset('letterer', this.filter.letterer, this.filterSettings.presets?.letterer, PersonRole.Letterer), - this.updateFromPreset('penciller', this.filter.penciller, this.filterSettings.presets?.penciller, PersonRole.Penciller), - this.updateFromPreset('publisher', this.filter.publisher, this.filterSettings.presets?.publisher, PersonRole.Publisher), - this.updateFromPreset('translators', this.filter.translators, this.filterSettings.presets?.translators, PersonRole.Translator) - ]).pipe(map(_ => { - return of(true); - })); - } + const clonedObj: any = {}; - fetchPeople(role: PersonRole, filter: string) { - return this.metadataService.getAllPeople(this.filter.libraries).pipe(map(people => { - return people.filter(p => p.role == role && this.utilityService.filter(p.name, filter)); - })); - } - - createBlankPersonSettings(id: string, role: PersonRole) { - var personSettings = new TypeaheadSettings(); - personSettings.minCharacters = 0; - personSettings.multiple = true; - personSettings.unique = true; - personSettings.addIfNonExisting = false; - personSettings.id = id; - personSettings.compareFn = (options: Person[], filter: string) => { - return options.filter(m => this.utilityService.filter(m.name, filter)); - } - - personSettings.selectionCompareFn = (a: Person, b: Person) => { - return a.name == b.name && a.role == b.role; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + if (typeof obj[key] === 'object' && obj[key] !== null) { + clonedObj[key] = this.deepClone(obj[key]); + } else { + clonedObj[key] = obj[key]; + } + } } - personSettings.fetchFn = (filter: string) => { - return this.fetchPeople(role, filter).pipe(map(items => personSettings.compareFn(items, filter))); - }; - return personSettings; - } - updateFormatFilters(formats: FilterItem[]) { - this.filter.formats = formats.map(item => item.value) || []; - this.formatSettings.savedData = formats; + return clonedObj; } - updateLibraryFilters(libraries: Library[]) { - this.filter.libraries = libraries.map(item => item.id) || []; - this.librarySettings.savedData = libraries; - } - - updateGenreFilters(genres: Genre[]) { - this.filter.genres = genres.map(item => item.id) || []; - this.genreSettings.savedData = genres; - } - - updateTagFilters(tags: Tag[]) { - this.filter.tags = tags.map(item => item.id) || []; - this.tagsSettings.savedData = tags; - } - - updatePersonFilters(persons: Person[], role: PersonRole) { - this.peopleSettings[role].savedData = persons; - switch (role) { - case PersonRole.CoverArtist: - this.filter.coverArtist = persons.map(p => p.id); - break; - case PersonRole.Character: - this.filter.character = persons.map(p => p.id); - break; - case PersonRole.Colorist: - this.filter.colorist = persons.map(p => p.id); - break; - case PersonRole.Editor: - this.filter.editor = persons.map(p => p.id); - break; - case PersonRole.Inker: - this.filter.inker = persons.map(p => p.id); - break; - case PersonRole.Letterer: - this.filter.letterer = persons.map(p => p.id); - break; - case PersonRole.Penciller: - this.filter.penciller = persons.map(p => p.id); - break; - case PersonRole.Publisher: - this.filter.publisher = persons.map(p => p.id); - break; - case PersonRole.Writer: - this.filter.writers = persons.map(p => p.id); - break; - case PersonRole.Translator: - this.filter.translators = persons.map(p => p.id); - - } - } - updateCollectionFilters(tags: CollectionTag[]) { - this.filter.collectionTags = tags.map(item => item.id) || []; - this.collectionSettings.savedData = tags; - } + loadFromPresetsAndSetup() { + this.fullyLoaded = false; - updateRating(rating: any) { - if (this.filterSettings.ratingDisabled) return; - this.filter.rating = rating; - } + this.filterV2 = this.deepClone(this.filterSettings.presetsV2); - updateAgeRating(ratingDtos: AgeRatingDto[]) { - this.filter.ageRating = ratingDtos.map(item => item.value) || []; - this.ageRatingSettings.savedData = ratingDtos; - } + this.sortGroup = new FormGroup({ + sortField: new FormControl({value: this.filterV2?.sortOptions?.sortField || SortField.SortName, disabled: this.filterSettings.sortDisabled}, []), + limitTo: new FormControl(this.filterV2?.limitTo || 0, []) + }); - updatePublicationStatus(dtos: PublicationStatusDto[]) { - this.filter.publicationStatus = dtos.map(item => item.value) || []; - this.publicationStatusSettings.savedData = dtos; - } + this.sortGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { + if (this.filterV2?.sortOptions === null) { + this.filterV2.sortOptions = { + isAscending: this.isAscendingSort, + sortField: parseInt(this.sortGroup.get('sortField')?.value, 10) + }; + } + this.filterV2!.sortOptions!.sortField = parseInt(this.sortGroup.get('sortField')?.value, 10); + this.filterV2!.limitTo = Math.max(parseInt(this.sortGroup.get('limitTo')?.value || '0', 10), 0); + this.cdRef.markForCheck(); + }); - updateLanguages(languages: Language[]) { - this.filter.languages = languages.map(item => item.isoCode) || []; - this.languageSettings.savedData = languages; + this.fullyLoaded = true; + this.apply(); } - updateReadStatus(status: string) { - if (status === 'read') { - this.filter.readStatus.read = !this.filter.readStatus.read; - } else if (status === 'inProgress') { - this.filter.readStatus.inProgress = !this.filter.readStatus.inProgress; - } else if (status === 'notRead') { - this.filter.readStatus.notRead = !this.filter.readStatus.notRead; - } - } updateSortOrder() { if (this.filterSettings.sortDisabled) return; this.isAscendingSort = !this.isAscendingSort; - if (this.filter.sortOptions === null) { - this.filter.sortOptions = { + if (this.filterV2?.sortOptions === null) { + this.filterV2.sortOptions = { isAscending: this.isAscendingSort, sortField: SortField.SortName } } - this.filter.sortOptions.isAscending = this.isAscendingSort; + this.filterV2!.sortOptions!.isAscending = this.isAscendingSort; } clear() { - this.filter = this.filterUtilitySerivce.createSeriesFilter(); - this.readProgressGroup.get('read')?.setValue(true); - this.readProgressGroup.get('notRead')?.setValue(true); - this.readProgressGroup.get('inProgress')?.setValue(true); - this.sortGroup.get('sortField')?.setValue(SortField.SortName); - this.isAscendingSort = true; - this.seriesNameGroup.get('seriesNameQuery')?.setValue(''); - this.cdRef.markForCheck(); - // Apply any presets which will trigger the apply + // Apply any presets which will trigger the "apply" this.loadFromPresetsAndSetup(); } apply() { - this.applyFilter.emit({filter: this.filter, isFirst: this.updateApplied === 0}); + this.applyFilter.emit({isFirst: this.updateApplied === 0, filterV2: this.filterV2!}); if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile && this.updateApplied !== 0) { this.toggleSelected(); @@ -661,4 +199,5 @@ export class MetadataFilterComponent implements OnInit { this.toggleService.set(!this.filteringCollapsed); } + protected readonly Breakpoint = Breakpoint; } diff --git a/UI/Web/src/app/nav/_components/events-widget/events-widget.component.ts b/UI/Web/src/app/nav/_components/events-widget/events-widget.component.ts index d03c36144e..7fa8d69d78 100644 --- a/UI/Web/src/app/nav/_components/events-widget/events-widget.component.ts +++ b/UI/Web/src/app/nav/_components/events-widget/events-widget.component.ts @@ -96,7 +96,6 @@ export class EventsWidgetComponent implements OnInit, OnDestroy { this.activeEvents += 1; this.cdRef.markForCheck(); } else if (event.event === EVENTS.UpdateAvailable) { - console.log('event: ', event); this.handleUpdateAvailableClick(event.payload); } }); diff --git a/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html b/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html index a3fa7fc8aa..63d6ab4a56 100644 --- a/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html +++ b/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html @@ -79,7 +79,7 @@ -
    +
    {{item.title}}
    @@ -97,7 +97,7 @@ -
    +
    diff --git a/UI/Web/src/app/nav/_components/nav-header/nav-header.component.ts b/UI/Web/src/app/nav/_components/nav-header/nav-header.component.ts index 8a381886a0..ac76fe6095 100644 --- a/UI/Web/src/app/nav/_components/nav-header/nav-header.component.ts +++ b/UI/Web/src/app/nav/_components/nav-header/nav-header.component.ts @@ -1,40 +1,44 @@ -import { DOCUMENT, NgIf, NgOptimizedImage, AsyncPipe } from '@angular/common'; +import {AsyncPipe, DOCUMENT, NgIf, NgOptimizedImage} from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, - Component, DestroyRef, + Component, + DestroyRef, ElementRef, inject, Inject, OnInit, ViewChild } from '@angular/core'; -import { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router'; -import { fromEvent } from 'rxjs'; -import { debounceTime, distinctUntilChanged, filter, tap } from 'rxjs/operators'; -import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service'; -import { Chapter } from 'src/app/_models/chapter'; -import { CollectionTag } from 'src/app/_models/collection-tag'; -import { Library } from 'src/app/_models/library'; -import { MangaFile } from 'src/app/_models/manga-file'; -import { PersonRole } from 'src/app/_models/metadata/person'; -import { ReadingList } from 'src/app/_models/reading-list'; -import { SearchResult } from 'src/app/_models/search/search-result'; -import { SearchResultGroup } from 'src/app/_models/search/search-result-group'; -import { AccountService } from 'src/app/_services/account.service'; -import { ImageService } from 'src/app/_services/image.service'; -import { NavService } from 'src/app/_services/nav.service'; -import { ScrollService } from 'src/app/_services/scroll.service'; -import { SearchService } from 'src/app/_services/search.service'; +import {NavigationEnd, Router, RouterLink, RouterLinkActive} from '@angular/router'; +import {fromEvent} from 'rxjs'; +import {debounceTime, distinctUntilChanged, filter, tap} from 'rxjs/operators'; +import {Chapter} from 'src/app/_models/chapter'; +import {CollectionTag} from 'src/app/_models/collection-tag'; +import {Library} from 'src/app/_models/library'; +import {MangaFile} from 'src/app/_models/manga-file'; +import {PersonRole} from 'src/app/_models/metadata/person'; +import {ReadingList} from 'src/app/_models/reading-list'; +import {SearchResult} from 'src/app/_models/search/search-result'; +import {SearchResultGroup} from 'src/app/_models/search/search-result-group'; +import {AccountService} from 'src/app/_services/account.service'; +import {ImageService} from 'src/app/_services/image.service'; +import {NavService} from 'src/app/_services/nav.service'; +import {ScrollService} from 'src/app/_services/scroll.service'; +import {SearchService} from 'src/app/_services/search.service'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import { SentenceCasePipe } from '../../../pipe/sentence-case.pipe'; -import { PersonRolePipe } from '../../../pipe/person-role.pipe'; -import { NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem } from '@ng-bootstrap/ng-bootstrap'; -import { EventsWidgetComponent } from '../events-widget/events-widget.component'; -import { SeriesFormatComponent } from '../../../shared/series-format/series-format.component'; -import { ImageComponent } from '../../../shared/image/image.component'; -import { GroupedTypeaheadComponent } from '../grouped-typeahead/grouped-typeahead.component'; +import {SentenceCasePipe} from '../../../pipe/sentence-case.pipe'; +import {PersonRolePipe} from '../../../pipe/person-role.pipe'; +import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '@ng-bootstrap/ng-bootstrap'; +import {EventsWidgetComponent} from '../events-widget/events-widget.component'; +import {SeriesFormatComponent} from '../../../shared/series-format/series-format.component'; +import {ImageComponent} from '../../../shared/image/image.component'; +import {GroupedTypeaheadComponent} from '../grouped-typeahead/grouped-typeahead.component'; import {TranslocoDirective} from "@ngneat/transloco"; +import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service"; +import {FilterStatement} from "../../../_models/metadata/v2/filter-statement"; +import {FilterField} from "../../../_models/metadata/v2/filter-field"; +import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison"; @Component({ selector: 'app-nav-header', @@ -58,10 +62,12 @@ export class NavHeaderComponent implements OnInit { backToTopNeeded = false; searchFocused: boolean = false; scrollElem: HTMLElement; + protected readonly FilterField = FilterField; constructor(public accountService: AccountService, private router: Router, public navService: NavService, public imageService: ImageService, @Inject(DOCUMENT) private document: Document, - private scrollService: ScrollService, private searchService: SearchService, private readonly cdRef: ChangeDetectorRef) { + private scrollService: ScrollService, private searchService: SearchService, private readonly cdRef: ChangeDetectorRef, + private filterUtilityService: FilterUtilitiesService) { this.scrollElem = this.document.body; } @@ -69,12 +75,11 @@ export class NavHeaderComponent implements OnInit { this.scrollService.scrollContainer$.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef), tap((scrollContainer) => { if (scrollContainer === 'body' || scrollContainer === undefined) { this.scrollElem = this.document.body; - fromEvent(this.document.body, 'scroll').pipe(debounceTime(20)).subscribe(() => this.checkBackToTopNeeded(this.document.body)); } else { const elem = scrollContainer as ElementRef; this.scrollElem = elem.nativeElement; - fromEvent(elem.nativeElement, 'scroll').pipe(debounceTime(20)).subscribe(() => this.checkBackToTopNeeded(elem.nativeElement)); } + fromEvent(this.scrollElem, 'scroll').pipe(debounceTime(20)).subscribe(() => this.checkBackToTopNeeded(this.scrollElem)); })).subscribe(); // Sometimes the top event emitter can be slow, so let's also check when a navigation occurs and recalculate @@ -125,49 +130,54 @@ export class NavHeaderComponent implements OnInit { }); } - goTo(queryParamName: string, filter: any) { + goTo(statement: FilterStatement) { let params: any = {}; - params[queryParamName] = filter; - params[FilterQueryParam.Page] = 1; + const filter = this.filterUtilityService.createSeriesV2Filter(); + filter.statements = [statement]; + params['page'] = 1; this.clearSearch(); - this.router.navigate(['all-series'], {queryParams: params}); + this.filterUtilityService.applyFilterWithParams(['all-series'], filter, params); + } + + goToOther(field: FilterField, value: string) { + this.goTo({field, comparison: FilterComparison.Equal, value}); } goToPerson(role: PersonRole, filter: any) { this.clearSearch(); switch(role) { case PersonRole.Writer: - this.goTo(FilterQueryParam.Writers, filter); + this.goTo({field: FilterField.Writers, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Artist: - this.goTo(FilterQueryParam.Artists, filter); + this.goTo({field: FilterField.CoverArtist, comparison: FilterComparison.Equal, value: filter}); // TODO: What is this supposed to be? break; case PersonRole.Character: - this.goTo(FilterQueryParam.Character, filter); + this.goTo({field: FilterField.Characters, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Colorist: - this.goTo(FilterQueryParam.Colorist, filter); + this.goTo({field: FilterField.Colorist, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Editor: - this.goTo(FilterQueryParam.Editor, filter); + this.goTo({field: FilterField.Editor, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Inker: - this.goTo(FilterQueryParam.Inker, filter); + this.goTo({field: FilterField.Inker, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.CoverArtist: - this.goTo(FilterQueryParam.CoverArtists, filter); + this.goTo({field: FilterField.CoverArtist, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Letterer: - this.goTo(FilterQueryParam.Letterer, filter); + this.goTo({field: FilterField.Letterer, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Penciller: - this.goTo(FilterQueryParam.Penciller, filter); + this.goTo({field: FilterField.Penciller, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Publisher: - this.goTo(FilterQueryParam.Publisher, filter); + this.goTo({field: FilterField.Publisher, comparison: FilterComparison.Equal, value: filter}); break; case PersonRole.Translator: - this.goTo(FilterQueryParam.Translator, filter); + this.goTo({field: FilterField.Translators, comparison: FilterComparison.Equal, value: filter}); break; } } @@ -232,4 +242,6 @@ export class NavHeaderComponent implements OnInit { hideSideNav() { this.navService.toggleSideNav(); } + + } diff --git a/UI/Web/src/app/pipe/compact-number.pipe.ts b/UI/Web/src/app/pipe/compact-number.pipe.ts index db56720781..f7f2d191a1 100644 --- a/UI/Web/src/app/pipe/compact-number.pipe.ts +++ b/UI/Web/src/app/pipe/compact-number.pipe.ts @@ -13,8 +13,11 @@ export class CompactNumberPipe implements PipeTransform { transform(value: number): string { // Weblate allows some non-standard languages, like 'zh_Hans', which should be just 'zh'. So we handle that here - const locale = localStorage.getItem(AccountService.localeKey)?.split('_')[0]; - return this.transformValue(locale || 'en', value); + const key = localStorage.getItem(AccountService.localeKey)?.replace('_', '-'); + if (key?.endsWith('Hans')) { + return this.transformValue(key?.split('-')[0] || 'en', value); + } + return this.transformValue(key || 'en', value); } private transformValue(locale: string, value: number) { diff --git a/UI/Web/src/app/pipe/safe-html.pipe.ts b/UI/Web/src/app/pipe/safe-html.pipe.ts index 90baad1c3e..979197de45 100644 --- a/UI/Web/src/app/pipe/safe-html.pipe.ts +++ b/UI/Web/src/app/pipe/safe-html.pipe.ts @@ -1,3 +1,4 @@ +import { inject } from '@angular/core'; import { Pipe, PipeTransform, SecurityContext } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; @@ -7,8 +8,8 @@ import { DomSanitizer } from '@angular/platform-browser'; standalone: true }) export class SafeHtmlPipe implements PipeTransform { - - constructor(private dom: DomSanitizer) {} + private readonly dom: DomSanitizer = inject(DomSanitizer); + constructor() {} transform(value: string): unknown { return this.dom.sanitize(SecurityContext.HTML, value); diff --git a/UI/Web/src/app/pipe/safe-style.pipe.ts b/UI/Web/src/app/pipe/safe-style.pipe.ts index fe59404549..8228ae1e09 100644 --- a/UI/Web/src/app/pipe/safe-style.pipe.ts +++ b/UI/Web/src/app/pipe/safe-style.pipe.ts @@ -1,3 +1,4 @@ +import { inject } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; @@ -6,9 +7,8 @@ import { DomSanitizer } from '@angular/platform-browser'; standalone: true }) export class SafeStylePipe implements PipeTransform { - - constructor(private sanitizer: DomSanitizer){ - } + private readonly sanitizer: DomSanitizer = inject(DomSanitizer); + constructor(){} transform(style: string) { return this.sanitizer.bypassSecurityTrustStyle(style); diff --git a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html index 231b8de77b..52af401e7e 100644 --- a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html +++ b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html @@ -26,7 +26,7 @@

    {{t('page-settings-title'
    - +

    @@ -38,8 +38,8 @@

    {{t('page-settings-title'
    -
    - +
    +
    @@ -52,7 +52,7 @@

    {{t('page-settings-title' {{t('continue')}} -
    +
    -
    diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts index f176152623..3d7c4df831 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts @@ -68,9 +68,9 @@ import { CarouselReelComponent } from '../../../carousel/_components/carousel-re import { SeriesMetadataDetailComponent } from '../series-metadata-detail/series-metadata-detail.component'; import { ImageComponent } from '../../../shared/image/image.component'; import { TagBadgeComponent } from '../../../shared/tag-badge/tag-badge.component'; -import { CardActionablesComponent } from '../../../cards/card-item/card-actionables/card-actionables.component'; import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component'; import {TranslocoDirective, TranslocoService} from "@ngneat/transloco"; +import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component"; interface RelatedSeriesPair { series: Series; diff --git a/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.html b/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.html index 99996a5d4d..37f2b4c492 100644 --- a/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.html +++ b/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.html @@ -26,11 +26,11 @@ - + {{item.title}} - + {{item.title}} @@ -56,7 +56,7 @@ - + @@ -64,55 +64,55 @@
    - + - + - + - + - + - + - + - + - + diff --git a/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts index ca7f9307fe..d340db42a4 100644 --- a/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges import { Router } from '@angular/router'; import { ReaderService } from 'src/app/_services/reader.service'; import {TagBadgeComponent, TagBadgeCursor} from '../../../shared/tag-badge/tag-badge.component'; -import { FilterQueryParam } from '../../../shared/_services/filter-utilities.service'; +import {FilterUtilitiesService} from '../../../shared/_services/filter-utilities.service'; import { UtilityService } from '../../../shared/_services/utility.service'; import { MangaFormat } from '../../../_models/manga-format'; import { ReadingList } from '../../../_models/reading-list'; @@ -21,6 +21,8 @@ import {SeriesInfoCardsComponent} from "../../../cards/series-info-cards/series- import {LibraryType} from "../../../_models/library"; import {MetadataDetailComponent} from "../metadata-detail/metadata-detail.component"; import {TranslocoDirective} from "@ngneat/transloco"; +import {FilterField} from "../../../_models/metadata/v2/filter-field"; +import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison"; @Component({ @@ -57,7 +59,10 @@ export class SeriesMetadataDetailComponent implements OnChanges { get LibraryType() { return LibraryType; } get MangaFormat() { return MangaFormat; } get TagBadgeCursor() { return TagBadgeCursor; } - get FilterQueryParam() { return FilterQueryParam; } + + get FilterField() { + return FilterField; + } get WebLinks() { if (this.seriesMetadata?.webLinks === '') return []; @@ -66,7 +71,7 @@ export class SeriesMetadataDetailComponent implements OnChanges { constructor(public utilityService: UtilityService, private router: Router, public readerService: ReaderService, - private readonly cdRef: ChangeDetectorRef) { + private readonly cdRef: ChangeDetectorRef, private filterUtilityService: FilterUtilitiesService) { } @@ -91,15 +96,13 @@ export class SeriesMetadataDetailComponent implements OnChanges { this.cdRef.markForCheck(); } - handleGoTo(event: {queryParamName: FilterQueryParam, filter: any}) { + handleGoTo(event: {queryParamName: FilterField, filter: any}) { this.goTo(event.queryParamName, event.filter); } - goTo(queryParamName: FilterQueryParam, filter: any) { - let params: any = {}; - params[queryParamName] = filter; - params[FilterQueryParam.Page] = 1; - this.router.navigate(['library', this.series.libraryId], {queryParams: params}); + goTo(queryParamName: FilterField, filter: any) { + this.filterUtilityService.applyFilter(['library', this.series.libraryId], queryParamName, + FilterComparison.Equal, filter); } navigate(basePage: string, id: number) { diff --git a/UI/Web/src/app/series-detail/series-detail.module.ts b/UI/Web/src/app/series-detail/series-detail.module.ts index e8e55b47e8..edf5085d92 100644 --- a/UI/Web/src/app/series-detail/series-detail.module.ts +++ b/UI/Web/src/app/series-detail/series-detail.module.ts @@ -14,7 +14,6 @@ import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.co import {ExternalSeriesCardComponent} from "../cards/external-series-card/external-series-card.component"; import {ExternalListItemComponent} from "../cards/external-list-item/external-list-item.component"; import {ListItemComponent} from "../cards/list-item/list-item.component"; -import {CardActionablesComponent} from "../cards/card-item/card-actionables/card-actionables.component"; import {SafeHtmlPipe} from "../pipe/safe-html.pipe"; import {TagBadgeComponent} from "../shared/tag-badge/tag-badge.component"; import {LoadingComponent} from "../shared/loading/loading.component"; @@ -37,6 +36,7 @@ import { import { SideNavCompanionBarComponent } from "../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component"; +import {CardActionablesComponent} from "../_single-module/card-actionables/card-actionables.component"; @NgModule({ diff --git a/UI/Web/src/app/shared/_services/download.service.ts b/UI/Web/src/app/shared/_services/download.service.ts index 70e8ffb7fd..f7e5441a80 100644 --- a/UI/Web/src/app/shared/_services/download.service.ts +++ b/UI/Web/src/app/shared/_services/download.service.ts @@ -228,7 +228,6 @@ export class DownloadService { ).pipe( throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }), download((blob, filename) => { - console.log('saving: ', filename) this.save(blob, decodeURIComponent(filename)); }), tap((d) => this.updateDownloadState(d, downloadType, subtitle)), diff --git a/UI/Web/src/app/shared/_services/filter-utilities.service.ts b/UI/Web/src/app/shared/_services/filter-utilities.service.ts index 4b47e10064..d16caedf0b 100644 --- a/UI/Web/src/app/shared/_services/filter-utilities.service.ts +++ b/UI/Web/src/app/shared/_services/filter-utilities.service.ts @@ -1,347 +1,209 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot } from '@angular/router'; -import { Pagination } from 'src/app/_models/pagination'; -import { SeriesFilter, SortField } from 'src/app/_models/metadata/series-filter'; - -/** - * Used to pass state between the filter and the url - */ -export enum FilterQueryParam { - Format = 'format', - Genres = 'genres', - AgeRating = 'ageRating', - PublicationStatus = 'publicationStatus', - Tags = 'tags', - Languages = 'languages', - CollectionTags = 'collectionTags', - Libraries = 'libraries', - Writers = 'writers', - Artists = 'artists', - Character = 'character', - Colorist = 'colorist', - CoverArtists = 'coverArtists', - Editor = 'editor', - Inker = 'inker', - Letterer = 'letterer', - Penciller = 'penciller', - Publisher = 'publisher', - Translator = 'translators', - ReadStatus = 'readStatus', - SortBy = 'sortBy', - Rating = 'rating', - Name = 'name', - /** - * This is a pagination control - */ - Page = 'page', - /** - * Special case for the UI. Does not trigger filtering - */ - None = 'none' -} +import {Injectable} from '@angular/core'; +import {ActivatedRouteSnapshot, Params, Router} from '@angular/router'; +import {Pagination} from 'src/app/_models/pagination'; +import {SortField, SortOptions} from 'src/app/_models/metadata/series-filter'; +import {MetadataService} from "../../_services/metadata.service"; +import {SeriesFilterV2} from "../../_models/metadata/v2/series-filter-v2"; +import {FilterStatement} from "../../_models/metadata/v2/filter-statement"; +import {FilterCombination} from "../../_models/metadata/v2/filter-combination"; +import {FilterField} from "../../_models/metadata/v2/filter-field"; +import {FilterComparison} from "../../_models/metadata/v2/filter-comparison"; + +const sortOptionsKey = 'sortOptions='; +const statementsKey = 'stmts='; +const limitToKey = 'limitTo='; +const combinationKey = 'combination='; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class FilterUtilitiesService { - constructor() { } - - /** - * Updates the window location with a custom url based on filter and pagination objects - * @param pagination - * @param filter - */ - updateUrlFromFilter(pagination: Pagination, filter: SeriesFilter | undefined) { - const params = '?page=' + pagination.currentPage; - - const url = this.urlFromFilter(window.location.href.split('?')[0] + params, filter); - window.history.replaceState(window.location.href, '', this.replacePaginationOnUrl(url, pagination)); - } - - /** - * Patches the page query param in the window location. - * @param pagination - */ - updateUrlFromPagination(pagination: Pagination) { - window.history.replaceState(window.location.href, '', this.replacePaginationOnUrl(window.location.href, pagination)); - } - - private replacePaginationOnUrl(url: string, pagination: Pagination) { - return url.replace(/page=\d+/i, 'page=' + pagination.currentPage); - } - - /** - * Will fetch current page from route if present - * @param ActivatedRouteSnapshot to fetch page from. Must be from component else may get stale data - * @param itemsPerPage If you want pagination, pass non-zero number - * @returns A default pagination object - */ - pagination(snapshot: ActivatedRouteSnapshot, itemsPerPage: number = 0): Pagination { - return {currentPage: parseInt(snapshot.queryParamMap.get('page') || '1', 10), itemsPerPage, totalItems: 0, totalPages: 1}; - } - - - /** - * Returns the current url with query params for the filter - * @param currentUrl Full url, with ?page=1 as a minimum - * @param filter Filter to build url off - * @returns current url with query params added - */ - urlFromFilter(currentUrl: string, filter: SeriesFilter | undefined) { - if (filter === undefined) return currentUrl; - let params = ''; - - params += this.joinFilter(filter.formats, FilterQueryParam.Format); - params += this.joinFilter(filter.genres, FilterQueryParam.Genres); - params += this.joinFilter(filter.ageRating, FilterQueryParam.AgeRating); - params += this.joinFilter(filter.publicationStatus, FilterQueryParam.PublicationStatus); - params += this.joinFilter(filter.tags, FilterQueryParam.Tags); - params += this.joinFilter(filter.languages, FilterQueryParam.Languages); - params += this.joinFilter(filter.collectionTags, FilterQueryParam.CollectionTags); - params += this.joinFilter(filter.libraries, FilterQueryParam.Libraries); - - params += this.joinFilter(filter.writers, FilterQueryParam.Writers); - params += this.joinFilter(filter.artists, FilterQueryParam.Artists); - params += this.joinFilter(filter.character, FilterQueryParam.Character); - params += this.joinFilter(filter.colorist, FilterQueryParam.Colorist); - params += this.joinFilter(filter.coverArtist, FilterQueryParam.CoverArtists); - params += this.joinFilter(filter.editor, FilterQueryParam.Editor); - params += this.joinFilter(filter.inker, FilterQueryParam.Inker); - params += this.joinFilter(filter.letterer, FilterQueryParam.Letterer); - params += this.joinFilter(filter.penciller, FilterQueryParam.Penciller); - params += this.joinFilter(filter.publisher, FilterQueryParam.Publisher); - params += this.joinFilter(filter.translators, FilterQueryParam.Translator); - - // readStatus (we need to do an additonal check as there is a default case) - if (filter.readStatus && filter.readStatus.inProgress !== true && filter.readStatus.notRead !== true && filter.readStatus.read !== true) { - params += `&${FilterQueryParam.ReadStatus}=${filter.readStatus.inProgress},${filter.readStatus.notRead},${filter.readStatus.read}`; - } + constructor(private metadataService: MetadataService, private router: Router) {} - // sortBy (additional check to not save to url if default case) - if (filter.sortOptions && !(filter.sortOptions.sortField === SortField.SortName && filter.sortOptions.isAscending === true)) { - params += `&${FilterQueryParam.SortBy}=${filter.sortOptions.sortField},${filter.sortOptions.isAscending}`; - } + applyFilter(page: Array, filter: FilterField, comparison: FilterComparison, value: string) { + const dto: SeriesFilterV2 = { + statements: [this.metadataService.createDefaultFilterStatement(filter, comparison, value + '')], + combination: FilterCombination.Or, + limitTo: 0 + }; - if (filter.rating > 0) { - params += `&${FilterQueryParam.Rating}=${filter.rating}`; + const url = this.urlFromFilterV2(page.join('/') + '?', dto); + return this.router.navigateByUrl(url); } - if (filter.seriesNameQuery !== '') { - params += `&${FilterQueryParam.Name}=${encodeURIComponent(filter.seriesNameQuery)}`; + applyFilterWithParams(page: Array, filter: SeriesFilterV2, extraParams: Params) { + let url = this.urlFromFilterV2(page.join('/') + '?', filter); + url += Object.keys(extraParams).map(k => `&${k}=${extraParams[k]}`).join(''); + return this.router.navigateByUrl(url, extraParams); } - return currentUrl + params; - } + /** + * Updates the window location with a custom url based on filter and pagination objects + * @param pagination + * @param filter + */ + updateUrlFromFilterV2(pagination: Pagination, filter: SeriesFilterV2 | undefined) { + const params = '?page=' + pagination.currentPage + '&'; - private joinFilter(filterProp: Array, key: string) { - let params = ''; - if (filterProp.length > 0) { - params += `&${key}=${filterProp.join(',')}`; - } - return params; - } - - /** - * Returns a new instance of a filterSettings that is populated with filter presets from URL - * @param ActivatedRouteSnapshot to fetch page from. Must be from component else may get stale data - * @returns The Preset filter and if something was set within - */ - filterPresetsFromUrl(snapshot: ActivatedRouteSnapshot): [SeriesFilter, boolean] { - const filter = this.createSeriesFilter(); - let anyChanged = false; - - const format = snapshot.queryParamMap.get(FilterQueryParam.Format); - if (format !== undefined && format !== null) { - filter.formats = [...filter.formats, ...format.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + const url = this.urlFromFilterV2(window.location.href.split('?')[0] + params, filter); + window.history.replaceState(window.location.href, '', this.replacePaginationOnUrl(url, pagination)); } - const genres = snapshot.queryParamMap.get(FilterQueryParam.Genres); - if (genres !== undefined && genres !== null) { - filter.genres = [...filter.genres, ...genres.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } - const ageRating = snapshot.queryParamMap.get(FilterQueryParam.AgeRating); - if (ageRating !== undefined && ageRating !== null) { - filter.ageRating = [...filter.ageRating, ...ageRating.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + private replacePaginationOnUrl(url: string, pagination: Pagination) { + return url.replace(/page=\d+/i, 'page=' + pagination.currentPage); } - const publicationStatus = snapshot.queryParamMap.get(FilterQueryParam.PublicationStatus); - if (publicationStatus !== undefined && publicationStatus !== null) { - filter.publicationStatus = [...filter.publicationStatus, ...publicationStatus.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + /** + * Will fetch current page from route if present + * @param snapshot to fetch page from. Must be from component else may get stale data + * @param itemsPerPage If you want pagination, pass non-zero number + * @returns A default pagination object + */ + pagination(snapshot: ActivatedRouteSnapshot, itemsPerPage: number = 0): Pagination { + return {currentPage: parseInt(snapshot.queryParamMap.get('page') || '1', 10), itemsPerPage, totalItems: 0, totalPages: 1}; } - const tags = snapshot.queryParamMap.get(FilterQueryParam.Tags); - if (tags !== undefined && tags !== null) { - filter.tags = [...filter.tags, ...tags.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } - const languages = snapshot.queryParamMap.get(FilterQueryParam.Languages); - if (languages !== undefined && languages !== null) { - filter.languages = [...filter.languages, ...languages.split(',')]; - anyChanged = true; - } + /** + * Returns the current url with query params for the filter + * @param currentUrl Full url, with ?page=1 as a minimum + * @param filter Filter to build url off + * @returns current url with query params added + */ + urlFromFilterV2(currentUrl: string, filter: SeriesFilterV2 | undefined) { + if (filter === undefined) return currentUrl; - const writers = snapshot.queryParamMap.get(FilterQueryParam.Writers); - if (writers !== undefined && writers !== null) { - filter.writers = [...filter.writers, ...writers.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + return currentUrl + this.encodeSeriesFilter(filter); } - const artists = snapshot.queryParamMap.get(FilterQueryParam.Artists); - if (artists !== undefined && artists !== null) { - filter.artists = [...filter.artists, ...artists.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + encodeSeriesFilter(filter: SeriesFilterV2) { + const encodedStatements = this.encodeFilterStatements(filter.statements); + const encodedSortOptions = filter.sortOptions ? `${sortOptionsKey}${this.encodeSortOptions(filter.sortOptions)}` : ''; + const encodedLimitTo = `${limitToKey}${filter.limitTo}`; - const character = snapshot.queryParamMap.get(FilterQueryParam.Character); - if (character !== undefined && character !== null) { - filter.character = [...filter.character, ...character.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + return `${this.encodeName(filter.name)}${encodedStatements}&${encodedSortOptions}&${encodedLimitTo}&${combinationKey}${filter.combination}`; } - const colorist = snapshot.queryParamMap.get(FilterQueryParam.Colorist); - if (colorist !== undefined && colorist !== null) { - filter.colorist = [...filter.colorist, ...colorist.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + encodeName(name: string | undefined) { + if (name === undefined || name === '') return ''; + return `name=${encodeURIComponent(name)}&` } - const coverArtists = snapshot.queryParamMap.get(FilterQueryParam.CoverArtists); - if (coverArtists !== undefined && coverArtists !== null) { - filter.coverArtist = [...filter.coverArtist, ...coverArtists.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } - const editor = snapshot.queryParamMap.get(FilterQueryParam.Editor); - if (editor !== undefined && editor !== null) { - filter.editor = [...filter.editor, ...editor.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + encodeSortOptions(sortOptions: SortOptions) { + return `sortField=${sortOptions.sortField}&isAscending=${sortOptions.isAscending}`; } - const inker = snapshot.queryParamMap.get(FilterQueryParam.Inker); - if (inker !== undefined && inker !== null) { - filter.inker = [...filter.inker, ...inker.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + encodeFilterStatements(statements: Array) { + if (statements.length === 0) return ''; + return statementsKey + encodeURIComponent(statements.map(statement => { + const encodedComparison = `comparison=${statement.comparison}`; + const encodedField = `field=${statement.field}`; + const encodedValue = `value=${encodeURIComponent(statement.value)}`; - const letterer = snapshot.queryParamMap.get(FilterQueryParam.Letterer); - if (letterer !== undefined && letterer !== null) { - filter.letterer = [...filter.letterer, ...letterer.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; + return `${encodedComparison}&${encodedField}&${encodedValue}`; + }).join(',')); } - const penciller = snapshot.queryParamMap.get(FilterQueryParam.Penciller); - if (penciller !== undefined && penciller !== null) { - filter.penciller = [...filter.penciller, ...penciller.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + filterPresetsFromUrlV2(snapshot: ActivatedRouteSnapshot): SeriesFilterV2 { + const filter = this.metadataService.createDefaultFilterDto(); + if (!window.location.href.includes('?')) return filter; - const publisher = snapshot.queryParamMap.get(FilterQueryParam.Publisher); - if (publisher !== undefined && publisher !== null) { - filter.publisher = [...filter.publisher, ...publisher.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + const queryParams = snapshot.queryParams; - const translators = snapshot.queryParamMap.get(FilterQueryParam.Translator); - if (translators !== undefined && translators !== null) { - filter.translators = [...filter.translators, ...translators.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + if (queryParams.name) { + filter.name = queryParams.name; + } - const libraries = snapshot.queryParamMap.get(FilterQueryParam.Libraries); - if (libraries !== undefined && libraries !== null) { - filter.libraries = [...filter.libraries, ...libraries.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + const fullUrl = window.location.href.split('?')[1]; + const stmtsStartIndex = fullUrl.indexOf(statementsKey); + let endIndex = fullUrl.indexOf('&' + sortOptionsKey); + if (endIndex < 0) { + endIndex = fullUrl.indexOf('&' + limitToKey); + } - const collectionTags = snapshot.queryParamMap.get(FilterQueryParam.CollectionTags); - if (collectionTags !== undefined && collectionTags !== null) { - filter.collectionTags = [...filter.collectionTags, ...collectionTags.split(',').map(item => parseInt(item, 10))]; - anyChanged = true; - } + if (stmtsStartIndex !== -1 || endIndex !== -1) { + // +1 is for the = + const stmtsEncoded = fullUrl.substring(stmtsStartIndex + statementsKey.length, endIndex); + filter.statements = this.decodeFilterStatements(stmtsEncoded); + } - // Rating, seriesName, - const rating = snapshot.queryParamMap.get(FilterQueryParam.Rating); - if (rating !== undefined && rating !== null && parseInt(rating, 10) > 0) { - filter.rating = parseInt(rating, 10); - anyChanged = true; - } + if (queryParams.sortOptions) { + const optionsStartIndex = fullUrl.indexOf('&' + sortOptionsKey); + const endIndex = fullUrl.indexOf('&' + limitToKey); + const sortOptionsEncoded = fullUrl.substring(optionsStartIndex + sortOptionsKey.length + 1, endIndex); + const sortOptions = this.decodeSortOptions(sortOptionsEncoded); + if (sortOptions) { + filter.sortOptions = sortOptions; + } + } + + if (queryParams.limitTo) { + filter.limitTo = parseInt(queryParams.limitTo, 10); + } + + if (queryParams.combination) { + filter.combination = parseInt(queryParams.combination, 10) as FilterCombination; + } - /// Read status is encoded as true,true,true - const readStatus = snapshot.queryParamMap.get(FilterQueryParam.ReadStatus); - if (readStatus !== undefined && readStatus !== null) { - const values = readStatus.split(',').map(i => i === 'true'); - if (values.length === 3) { - filter.readStatus.inProgress = values[0]; - filter.readStatus.notRead = values[1]; - filter.readStatus.read = values[2]; - anyChanged = true; - } + return filter; } - const sortBy = snapshot.queryParamMap.get(FilterQueryParam.SortBy); - if (sortBy !== undefined && sortBy !== null) { - const values = sortBy.split(','); - if (values.length === 1) { - values.push('true'); - } - if (values.length === 2) { - filter.sortOptions = { - isAscending: values[1] === 'true', - sortField: Number(values[0]) + decodeSortOptions(encodedSortOptions: string): SortOptions | null { + const parts = decodeURIComponent(encodedSortOptions).split('&'); + const sortFieldPart = parts.find(part => part.startsWith('sortField=')); + const isAscendingPart = parts.find(part => part.startsWith('isAscending=')); + + if (sortFieldPart && isAscendingPart) { + const sortField = parseInt(sortFieldPart.split('=')[1], 10) as SortField; + const isAscending = isAscendingPart.split('=')[1] === 'true'; + return {sortField, isAscending}; } - anyChanged = true; - } + + return null; + } + + decodeFilterStatements(encodedStatements: string): FilterStatement[] { + const statementStrings = decodeURIComponent(encodedStatements).split(','); + return statementStrings.map(statementString => { + const parts = statementString.split('&'); + if (parts === null || parts.length < 3) return null; + + const comparisonStartToken = parts.find(part => part.startsWith('comparison=')); + if (!comparisonStartToken) return null; + const comparison = parseInt(comparisonStartToken.split('=')[1], 10) as FilterComparison; + + const fieldStartToken = parts.find(part => part.startsWith('field=')); + if (!fieldStartToken) return null; + const field = parseInt(fieldStartToken.split('=')[1], 10) as FilterField; + + const valueStartToken = parts.find(part => part.startsWith('value=')); + if (!valueStartToken) return null; + const value = decodeURIComponent(valueStartToken.split('=')[1]); + return {comparison, field, value}; + }).filter(o => o != null) as FilterStatement[]; } - const searchNameQuery = snapshot.queryParamMap.get(FilterQueryParam.Name); - if (searchNameQuery !== undefined && searchNameQuery !== null && searchNameQuery !== '') { - filter.seriesNameQuery = decodeURIComponent(searchNameQuery); - anyChanged = true; + createSeriesV2Filter(): SeriesFilterV2 { + return { + combination: FilterCombination.And, + statements: [], + limitTo: 0, + sortOptions: { + isAscending: true, + sortField: SortField.SortName + }, + }; } + createSeriesV2DefaultStatement(): FilterStatement { + return { + comparison: FilterComparison.Equal, + value: '', + field: FilterField.SeriesName + } + } - return [filter, false]; // anyChanged. Testing out if having a filter active but keep drawer closed by default works better - } - - createSeriesFilter(filter?: SeriesFilter) { - if (filter !== undefined) return filter; - const data: SeriesFilter = { - formats: [], - libraries: [], - genres: [], - writers: [], - artists: [], - penciller: [], - inker: [], - colorist: [], - letterer: [], - coverArtist: [], - editor: [], - publisher: [], - character: [], - translators: [], - collectionTags: [], - rating: 0, - readStatus: { - read: true, - inProgress: true, - notRead: true - }, - sortOptions: null, - ageRating: [], - tags: [], - languages: [], - publicationStatus: [], - seriesNameQuery: '', - releaseYearRange: null - }; - - return data; - } } diff --git a/UI/Web/src/app/shared/_services/utility.service.ts b/UI/Web/src/app/shared/_services/utility.service.ts index 8012321329..3dbb6f3ba6 100644 --- a/UI/Web/src/app/shared/_services/utility.service.ts +++ b/UI/Web/src/app/shared/_services/utility.service.ts @@ -155,6 +155,7 @@ export class UtilityService { } return true; } + private isObject(object: any) { return object != null && typeof object === 'object'; } diff --git a/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html b/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html index daa0f00028..9f4714f3b9 100644 --- a/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html +++ b/UI/Web/src/app/sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component.html @@ -14,6 +14,7 @@
    +
    +
    + +
    +
    +
    diff --git a/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.ts b/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.ts index fadc58511b..23f2f10a32 100644 --- a/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.ts +++ b/UI/Web/src/app/statistics/_components/user-stats/user-stats.component.ts @@ -1,26 +1,19 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - DestroyRef, - inject, - OnInit -} from '@angular/core'; -import { map, Observable, shareReplay } from 'rxjs'; -import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service'; -import { UserReadStatistics } from 'src/app/statistics/_models/user-read-statistics'; -import { StatisticsService } from 'src/app/_services/statistics.service'; -import { ReadHistoryEvent } from '../../_models/read-history-event'; -import { MemberService } from 'src/app/_services/member.service'; -import { AccountService } from 'src/app/_services/account.service'; -import { PieDataItem } from '../../_models/pie-data-item'; -import { LibraryService } from 'src/app/_services/library.service'; -import { PercentPipe, NgIf, AsyncPipe } from '@angular/common'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core'; +import {map, Observable, shareReplay} from 'rxjs'; +import {UserReadStatistics} from 'src/app/statistics/_models/user-read-statistics'; +import {StatisticsService} from 'src/app/_services/statistics.service'; +import {ReadHistoryEvent} from '../../_models/read-history-event'; +import {MemberService} from 'src/app/_services/member.service'; +import {AccountService} from 'src/app/_services/account.service'; +import {PieDataItem} from '../../_models/pie-data-item'; +import {LibraryService} from 'src/app/_services/library.service'; +import {AsyncPipe, NgIf, PercentPipe} from '@angular/common'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import { StatListComponent } from '../stat-list/stat-list.component'; -import { ReadingActivityComponent } from '../reading-activity/reading-activity.component'; -import { UserStatsInfoCardsComponent } from '../user-stats-info-cards/user-stats-info-cards.component'; +import {StatListComponent} from '../stat-list/stat-list.component'; +import {ReadingActivityComponent} from '../reading-activity/reading-activity.component'; +import {UserStatsInfoCardsComponent} from '../user-stats-info-cards/user-stats-info-cards.component'; import {TranslocoModule} from "@ngneat/transloco"; +import {DayBreakdownComponent} from "../day-breakdown/day-breakdown.component"; @Component({ selector: 'app-user-stats', @@ -35,6 +28,7 @@ import {TranslocoModule} from "@ngneat/transloco"; StatListComponent, AsyncPipe, TranslocoModule, + DayBreakdownComponent, ], }) export class UserStatsComponent implements OnInit { @@ -47,7 +41,7 @@ export class UserStatsComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); constructor(private readonly cdRef: ChangeDetectorRef, private statService: StatisticsService, - private filterService: FilterUtilitiesService, private accountService: AccountService, private memberService: MemberService, + private accountService: AccountService, private memberService: MemberService, private libraryService: LibraryService) { this.isAdmin$ = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), map(u => { if (!u) return false; @@ -57,8 +51,6 @@ export class UserStatsComponent implements OnInit { } ngOnInit(): void { - const filter = this.filterService.createSeriesFilter(); - filter.readStatus = {read: true, notRead: false, inProgress: true}; this.memberService.getMember().subscribe(me => { this.userId = me.id; this.cdRef.markForCheck(); diff --git a/UI/Web/src/app/typeahead/_components/typeahead.component.ts b/UI/Web/src/app/typeahead/_components/typeahead.component.ts index 06a5b1ae08..19ef6e79cf 100644 --- a/UI/Web/src/app/typeahead/_components/typeahead.component.ts +++ b/UI/Web/src/app/typeahead/_components/typeahead.component.ts @@ -509,11 +509,8 @@ export class TypeaheadComponent implements OnInit { // Check if this new option will interfere with any existing ones not shown if (typeof this.settings.compareFnForAdd == 'function') { - console.log('filtered options: ', this.optionSelection.selected()); const willDuplicateExist = this.settings.compareFnForAdd(this.optionSelection.selected(), inputText); - console.log('duplicate check: ', willDuplicateExist); if (willDuplicateExist.length > 0) { - console.log("can't show add, duplicates will exist"); return; } } @@ -521,10 +518,7 @@ export class TypeaheadComponent implements OnInit { if (typeof this.settings.compareFn == 'function') { // The problem here is that compareFn can report that duplicate will exist as it does contains not match const matches = this.settings.compareFn(options, inputText); - console.log('matches for ', inputText, ': ', matches); - console.log('matches include input string: ', matches.includes(this.settings.addTransformFn(inputText))); if (matches.length > 0 && matches.includes(this.settings.addTransformFn(inputText))) { - console.log("can't show add, there are still "); return; } } diff --git a/UI/Web/src/app/user-settings/api-key/api-key.component.html b/UI/Web/src/app/user-settings/api-key/api-key.component.html index b5052f0b31..3773173903 100644 --- a/UI/Web/src/app/user-settings/api-key/api-key.component.html +++ b/UI/Web/src/app/user-settings/api-key/api-key.component.html @@ -3,11 +3,16 @@   {{tooltipText}}
    - -
    - - -
    + + + + {{t('regen-warning')}} diff --git a/UI/Web/src/app/user-settings/api-key/api-key.component.ts b/UI/Web/src/app/user-settings/api-key/api-key.component.ts index 7b85899b10..c2612447d9 100644 --- a/UI/Web/src/app/user-settings/api-key/api-key.component.ts +++ b/UI/Web/src/app/user-settings/api-key/api-key.component.ts @@ -14,7 +14,7 @@ import {Clipboard} from '@angular/cdk/clipboard'; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { NgIf } from '@angular/common'; -import {TranslocoDirective, TranslocoService} from "@ngneat/transloco"; +import {translate, TranslocoDirective} from "@ngneat/transloco"; @Component({ selector: 'app-api-key', @@ -30,11 +30,14 @@ export class ApiKeyComponent implements OnInit { @Input() showRefresh: boolean = true; @Input() transform: (val: string) => string = (val: string) => val; @Input() tooltipText: string = ''; + @Input() hideData = true; @ViewChild('apiKey') inputElem!: ElementRef; key: string = ''; private readonly destroyRef = inject(DestroyRef); - private readonly translocoService = inject(TranslocoService); + get InputType() { + return this.hideData ? 'password' : 'text'; + } constructor(private confirmService: ConfirmService, private accountService: AccountService, private toastr: ToastrService, private clipboard: Clipboard, private readonly cdRef: ChangeDetectorRef) { } @@ -45,7 +48,7 @@ export class ApiKeyComponent implements OnInit { if (user) { key = user.apiKey; } else { - key = this.translocoService.translate('api-key.no-key'); + key = translate('api-key.no-key'); } if (this.transform != undefined) { @@ -63,13 +66,13 @@ export class ApiKeyComponent implements OnInit { } async refresh() { - if (!await this.confirmService.confirm(this.translocoService.translate('api-key.confirm-reset'))) { + if (!await this.confirmService.confirm(translate('api-key.confirm-reset'))) { return; } this.accountService.resetApiKey().subscribe(newKey => { this.key = newKey; this.cdRef.markForCheck(); - this.toastr.success(this.translocoService.translate('api-key.key-reset')); + this.toastr.success(translate('api-key.key-reset')); }); } @@ -80,4 +83,9 @@ export class ApiKeyComponent implements OnInit { } } + show() { + this.inputElem.nativeElement.setAttribute('type', 'text'); + this.cdRef.markForCheck(); + } + } diff --git a/UI/Web/src/app/user-settings/change-email/change-email.component.html b/UI/Web/src/app/user-settings/change-email/change-email.component.html index 57da944830..e736ad4500 100644 --- a/UI/Web/src/app/user-settings/change-email/change-email.component.html +++ b/UI/Web/src/app/user-settings/change-email/change-email.component.html @@ -62,7 +62,7 @@

    {{t('email-label')}}

    {{t('email-updated-title')}}

    {{t('email-updated-description')}}

    - + diff --git a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html index 5ef2ff930b..c3dee6f1c1 100644 --- a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html +++ b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html @@ -416,8 +416,8 @@

    {{t('clients-opds-description')}}

    - - + +
    diff --git a/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.html b/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.html index c179d374cd..7bf9832f8c 100644 --- a/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.html +++ b/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.html @@ -6,7 +6,7 @@

    {{t('title')}}

    -

    {{t('series-count', {num: (seriesPagination.totalItems | number)})}}
    +
    {{t('series-count', {num: (pagination.totalItems | number)})}}
    @@ -16,7 +16,7 @@
    {{t('series-count', {num: (seriesPagination.totalItems | number)})} = []; - seriesPagination!: Pagination; - filter: SeriesFilter | undefined = undefined; + pagination!: Pagination; + filter: SeriesFilterV2 | undefined = undefined; filterSettings: FilterSettings = new FilterSettings(); refresh: EventEmitter = new EventEmitter(); - filterActiveCheck!: SeriesFilter; + filterActiveCheck!: SeriesFilterV2; filterActive: boolean = false; jumpbarKeys: Array = []; @@ -84,7 +85,6 @@ export class WantToReadComponent implements OnInit, AfterContentChecked { } collectionTag: any; - tagImage: any; get ScrollingBlockHeight() { if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)'; @@ -104,11 +104,18 @@ export class WantToReadComponent implements OnInit, AfterContentChecked { private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService, private hubService: MessageHubService, private jumpbarService: JumpbarService) { this.router.routeReuseStrategy.shouldReuseRoute = () => false; - this.titleService.setTitle('Want To Read'); + this.titleService.setTitle('Kavita - ' + translate('want-to-read.title')); + + this.pagination = this.filterUtilityService.pagination(this.route.snapshot); + + this.filter = this.filterUtilityService.filterPresetsFromUrlV2(this.route.snapshot); + if (this.filter.statements.length === 0) { + this.filter!.statements.push(this.filterUtilityService.createSeriesV2DefaultStatement()); + } + this.filterActiveCheck = this.filterUtilityService.createSeriesV2Filter(); + this.filterActiveCheck!.statements.push(this.filterUtilityService.createSeriesV2DefaultStatement()); + this.filterSettings.presetsV2 = this.filter; - this.seriesPagination = this.filterUtilityService.pagination(this.route.snapshot); - [this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot); - this.filterActiveCheck = this.filterUtilityService.createSeriesFilter(); this.cdRef.markForCheck(); this.hubService.messages$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { @@ -120,7 +127,7 @@ export class WantToReadComponent implements OnInit, AfterContentChecked { } this.series = this.series.filter(s => s.id != seriesRemoved.seriesId); - this.seriesPagination.totalItems--; + this.pagination.totalItems--; this.cdRef.markForCheck(); this.refresh.emit(); } @@ -156,7 +163,7 @@ export class WantToReadComponent implements OnInit, AfterContentChecked { removeSeries(seriesId: number) { this.series = this.series.filter(s => s.id != seriesId); - this.seriesPagination.totalItems--; + this.pagination.totalItems--; this.cdRef.markForCheck(); this.refresh.emit(); } @@ -168,7 +175,7 @@ export class WantToReadComponent implements OnInit, AfterContentChecked { this.seriesService.getWantToRead(undefined, undefined, this.filter).pipe(take(1)).subscribe(paginatedList => { this.series = paginatedList.result; - this.seriesPagination = paginatedList.pagination; + this.pagination = paginatedList.pagination; this.jumpbarKeys = this.jumpbarService.getJumpKeys(this.series, (series: Series) => series.name); this.isLoading = false; window.scrollTo(0, 0); @@ -177,27 +184,14 @@ export class WantToReadComponent implements OnInit, AfterContentChecked { } updateFilter(data: FilterEvent) { - this.filter = data.filter; + if (data.filterV2 === undefined) return; + this.filter = data.filterV2; - if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.seriesPagination, this.filter); - this.loadPage(); - } + if (!data.isFirst) { + this.filterUtilityService.updateUrlFromFilterV2(this.pagination, this.filter); + } - handleAction(action: ActionItem, series: Series) { - // let lib: Partial = library; - // if (library === undefined) { - // lib = {id: this.libraryId, name: this.libraryName}; - // } - // switch (action.action) { - // case(Action.Scan): - // this.actionService.scanLibrary(lib); - // break; - // case(Action.RefreshMetadata): - // this.actionService.refreshMetadata(lib); - // break; - // default: - // break; - // } + this.loadPage(); } } diff --git a/UI/Web/src/assets/langs/cs.json b/UI/Web/src/assets/langs/cs.json new file mode 100644 index 0000000000..6be25663f5 --- /dev/null +++ b/UI/Web/src/assets/langs/cs.json @@ -0,0 +1,1755 @@ +{ + "login": { + "title": "Přihlásit se", + "username": "{{common.username}}", + "password": "{{common.password}}", + "password-validation": "{{validation.password-validation}}", + "forgot-password": "Zapomenuté heslo?", + "submit": "{{common.submit}}" + }, + "dashboard": { + "no-libraries": "Zatím nejsou nastaveny žádné knihovny. Nakonfigurujte některé v", + "server-settings-link": "Nastavení serveru", + "not-granted": "Nebyl vám udělen přístup k žádným knihovnám.", + "on-deck-title": "Na palubě", + "recently-updated-title": "Nedávno aktualizovaná řada", + "recently-added-title": "Nově přidaná série" + }, + "edit-user": { + "edit": "{{common.edit}}", + "close": "{{common.close}}", + "username": "{{common.username}}", + "required": "{{validation.required-field}}", + "email": "{{common.email}}", + "not-valid-email": "{{validation.valid-email}}", + "cancel": "{{common.cancel}}", + "saving": "Ukládání…", + "update": "Aktualizace" + }, + "user-scrobble-history": { + "title": "Scrobble historie", + "description": "Zde najdete všechny scrobble události spojené s vaším účtem. Aby události existovaly, musíte mít nakonfigurovaného aktivního poskytovatele scrobble. Všechny události, které byly zpracovány, budou po měsíci vymazány. Pokud existují nezpracované události, je pravděpodobné, že nemohou tvořit shody proti proudu. Obraťte se prosím na svého administrátora a nechte je opravit.", + "filter-label": "Filtr", + "created-header": "Vytvořeno", + "last-modified-header": "Naposledy změněno", + "type-header": "Typ", + "series-header": "Série", + "data-header": "Data", + "is-processed-header": "Je zpracováno", + "no-data": "Žádná data", + "volume-and-chapter-num": "Svazek {{v}} Kapitola {{n}}", + "rating": "Hodnocení {{r}}", + "not-applicable": "Nelze použít", + "processed": "Zpracováno", + "not-processed": "Nezpracováno" + }, + "scrobble-event-type-pipe": { + "chapter-read": "Pokrok ve čtení", + "score-updated": "Aktualizace hodnocení", + "want-to-read-add": "Chcete si přečíst: Přidat", + "want-to-read-remove": "Chcete si přečíst: Odebrat", + "review": "Aktualizace recenze" + }, + "spoiler": { + "click-to-show": "Spoiler, kliknutím zobrazíte" + }, + "review-series-modal": { + "title": "Upravit recenzi", + "tagline-label": "Slogan, popis", + "review-label": "Recenze", + "close": "{{common.close}}", + "save": "{{common.save}}" + }, + "review-card-modal": { + "close": "{{common.close}}", + "user-review": "{{username}}'s Recenze", + "external-mod": "(externí)", + "go-to-review": "Přejít na recenzi" + }, + "review-card": { + "your-review": "Toto je vaše recenze", + "external-review": "Externí recenze", + "local-review": "Recenze", + "rating-percentage": "Hodnocení {{r}} %" + }, + "want-to-read": { + "title": "Chci číst", + "series-count": "{{common.series-count}}", + "no-items": "Nejsou žádné položky. Zkuste přidat sérii.", + "no-items-filtered": "Vašemu aktuálnímu filtru neodpovídají žádné položky." + }, + "user-preferences": { + "title": "Uživatelský panel", + "pref-description": "Toto jsou globální nastavení, která jsou vázána na váš účet.", + "account-tab": "Účet", + "preferences-tab": "Předvolby", + "3rd-party-clients-tab": "Klienti třetích stran", + "theme-tab": "Téma", + "devices-tab": "Zařízení", + "stats-tab": "Statistiky", + "scrobbling-tab": "Scrobbling", + "success-toast": "Uživatelské předvolby byly aktualizovány", + "global-settings-title": "Globální nastavení", + "page-layout-mode-label": "Režim rozvržení stránky", + "page-layout-mode-tooltip": "Zobrazit položky jako karty nebo zobrazení seznamu na stránce Podrobnosti série.", + "locale-label": "Národní prostředí", + "locale-tooltip": "Jazyk, který má Kavita používat", + "blur-unread-summaries-label": "Rozmazání nepřečtených souhrnů", + "blur-unread-summaries-tooltip": "Rozmaže souhrnný text ve svazcích nebo kapitolách, které nemají žádný pokrok ve čtení (aby se zabránilo spoilerům)", + "prompt-on-download-label": "Výzva ke stažení", + "prompt-on-download-tooltip": "Dotázat se, když velikost stahování přesáhne {{size}}MB", + "disable-animations-label": "Zakázat animace", + "disable-animations-tooltip": "Vypne animace na webu. Užitečné pro e-ink čtečky.", + "collapse-series-relationships-label": "Sbalit vztahy série", + "collapse-series-relationships-tooltip": "Měla by Kavita ukázat seriály, které nemají žádné vztahy nebo jsou rodičem/prequelem", + "share-series-reviews-label": "Sdílejte recenze Serií", + "share-series-reviews-tooltip": "Má Kavita zahrnout vaše recenze na Series pro ostatní uživatele", + "image-reader-settings-title": "Čtečka obrázků", + "reading-direction-label": "Směr čtení", + "reading-direction-tooltip": "Kliknutím na směr přejdete na další stránku. Zprava doleva znamená, že kliknutím na levou stranu obrazovky přejdete na další stránku.", + "scaling-option-label": "Možnosti škálování", + "scaling-option-tooltip": "Jak změnit měřítko obrázku na obrazovku.", + "page-splitting-label": "Rozdělení stránky", + "page-splitting-tooltip": "Jak rozdělit obrázek v plné šířce (tj. oba obrázky jsou kombinovány)", + "reading-mode-label": "Režim čtení", + "layout-mode-label": "Režim rozvržení", + "layout-mode-tooltip": "Vykreslení jednoho obrázku na obrazovku nebo dvou obrázků vedle sebe", + "background-color-label": "Barva pozadí", + "auto-close-menu-label": "Nabídka automatického zavření", + "show-screen-hints-label": "Zobrazit tipy na obrazovce", + "emulate-comic-book-label": "Emulovat komiks", + "swipe-to-paginate-label": "Přejeďte prstem na stránkování", + "book-reader-settings-title": "Čtečka knih", + "tap-to-paginate-label": "Klepnutím můžete stránkovat", + "tap-to-paginate-tooltip": "Pokud strany obrazovky čtečky knih umožňují klepnutím na ni přejít na předchozí/další stránku", + "immersive-mode-label": "Imerzní režim", + "immersive-mode-tooltip": "Tím se nabídka skryje za kliknutí na dokument čtečky a otočením klepnutím zapněte stránkování", + "reading-direction-book-label": "Směr čtení", + "reading-direction-book-tooltip": "Směrem ke kliknutí se přesunete na další stránku. Zprava doleva znamená, že kliknutím na levou stranu obrazovky přejdete na další stránku.", + "font-family-label": "Rodina písem", + "font-family-tooltip": "Rodina písem k načtení. Výchozí načte výchozí písmo knihy", + "writing-style-label": "Styl psaní", + "writing-style-tooltip": "Změní směr textu. Vodorovně je zleva doprava, svisle shora dolů.", + "layout-mode-book-label": "Režim rozvržení", + "layout-mode-book-tooltip": "Jak by měl být obsah rozvržen. Scroll je takový, jak ho kniha balí. 1 nebo 2 sloupce se přizpůsobí výšce zařízení a vejdou se 1 nebo 2 sloupce textu na stránku", + "color-theme-book-label": "Barevný motiv", + "color-theme-book-tooltip": "Jaký barevný motiv použít na obsah a nabídku čtečky knih", + "font-size-book-label": "Velikost písma", + "line-height-book-label": "Řádkování", + "line-height-book-tooltip": "Kolik mezer mezi řádky knihy", + "margin-book-label": "Okraj", + "margin-book-tooltip": "Kolik mezer na každé straně obrazovky. To bude přepsáno na 0 na mobilních zařízeních bez ohledu na toto nastavení.", + "clients-opds-alert": "OPDS není na tomto serveru povoleno. Toto nebude mít vliv na uživatele Tachiyomi.", + "clients-opds-description": "Všichni klienti třetích stran budou používat klíč API nebo níže uvedenou adresu URL připojení. Jsou to jako hesla, udržujte je v soukromí.", + "clients-api-key-tooltip": "Klíč API je jako heslo. Udržujte to v tajnosti, Udržujte to v bezpečí.", + "clients-opds-url-tooltip": "OPDS URL", + "reset": "{{common.reset}}", + "save": "{{common.save}}" + }, + "user-holds": { + "title": "Scrobble drží", + "description": "Toto je uživatelsky spravovaný seznam Sérií, který nebude scrobován na upstream poskytovatele. Sérii můžete kdykoli odstranit a další událost s možností scrobble (průběh čtení, hodnocení, stav chtít číst) spustí události." + }, + "theme-manager": { + "title": "Správce motivů", + "looking-for-theme": "Hledáte téma světla nebo e-ink? Máme několik vlastních motivů, které můžete použít u nás ", + "looking-for-theme-continued": "téma github.", + "scan": "Skenovat", + "site-themes": "Témata stránek", + "set-default": "Nastavit výchozí", + "apply": "{{common.apply}}", + "applied": "Aplikovaný", + "updated-toastr": "Výchozí nastavení webu bylo aktualizováno na {{name}}", + "scan-queued": "Kontrola motivu webu byla zařazena do fronty" + }, + "theme": { + "theme-dark": "Temný", + "theme-black": "Černá", + "theme-paper": "Papír", + "theme-white": "Bílý" + }, + "restriction-selector": { + "title": "Věkové omezení", + "description": "Po výběru budou z výsledků odstraněny všechny série a seznamy čtení, které mají alespoň jednu položku větší než vybrané omezení.", + "not-applicable-for-admins": "Toto neplatí pro administrátory.", + "age-rating-label": "Věkové hodnocení", + "no-restriction": "Bez omezení", + "include-unknowns-label": "Zahrnout neznámé", + "include-unknowns-tooltip": "Pokud je pravda, budou Neznámé povoleny s věkovým omezením. To by mohlo vést k úniku neoznačených médií k uživatelům s věkovým omezením." + }, + "site-theme-provider-pipe": { + "system": "Systém", + "user": "Uživatel" + }, + "manage-devices": { + "title": "Správce zařízení", + "description": "Tato část je určena k nastavení zařízení, která se nemohou připojit ke Kavitě prostřednictvím webového prohlížeče a místo toho mají e-mailovou adresu, která přijímá soubory.", + "devices-title": "Zařízení", + "no-devices": "Zatím nejsou nastavena žádná zařízení", + "platform-label": "Platforma: ", + "email-label": "Email: ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" + }, + "edit-device": { + "device-name-label": "Název zařízení", + "email-label": "{{common.email}}", + "email-tooltip": "Tento e-mail bude použit k přijetí souboru prostřednictvím Odeslat", + "device-platform-label": "Platforma zařízení", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" + }, + "change-password": { + "password-label": "{{common.password}}", + "current-password-label": "Aktuální heslo", + "new-password-label": "Nové heslo", + "confirm-password-label": "Potvrďte heslo", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "Heslo se musí shodovat", + "permission-error": "Nemáte oprávnění ke změně hesla. Obraťte se na správce serveru." + }, + "change-email": { + "email-label": "{{common.email}}", + "current-password-label": "Aktuální heslo", + "email-not-confirmed": "Tento e-mail není potvrzen", + "email-updated-title": "Email aktualizován", + "email-updated-description": "Pro potvrzení e-mailu pro váš účet můžete použít následující odkaz níže. Pokud je váš server externě přístupný, bude na e-mail odeslán e-mail a odkaz lze použít k potvrzení e-mailu.", + "setup-user-account": "Nastavení uživatelského účtu", + "invite-url-label": "Adresa URL pozvánky", + "invite-url-tooltip": "Zkopírujte toto a vložte na novou kartu", + "permission-error": "Nemáte oprávnění změnit svůj e-mail. Obraťte se na správce serveru.", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "change-age-restriction": { + "age-restriction-label": "Věkové omezení", + "unknowns": "Neznámí", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "api-key": { + "copy": "Kopírovat", + "show": "Ukázat", + "regen-warning": "Obnovení klíče API zruší platnost všech stávajících klientů.", + "no-key": "CHYBA – KLÍČ NENÍ NASTAVEN", + "confirm-reset": "Tím zrušíte platnost všech konfigurací OPDS, které jste nastavili. Jste si jistý, že chcete pokračovat?", + "key-reset": "Obnovení klíče API" + }, + "scrobbling-providers": { + "title": "Poskytovatelé scroblingu", + "requires": "Tato funkce vyžaduje aktivní licenci {{product}}", + "token-expired": "Platnost tokenu vypršela", + "no-token-set": "Žádná sada tokenů", + "token-set": "Sada tokenů", + "generate": "Generovat", + "instructions": "První uživatelé by měli kliknout na „{{scrobbling-providers.generate}}“ níže, aby Kavita+ mohla mluvit se {{service}}. Jakmile program autorizujete, zkopírujte a vložte token do níže uvedeného vstupu. Svůj token můžete kdykoli obnovit.", + "token-input-label": "Token {{service}} je zde", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "typeahead": { + "locked-field": "Pole je uzamčeno", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "add-item": "Přidat {{item}}…", + "no-data": "Žádné údaje", + "add-custom-item": ", zadejte pro přidání vlastní položky" + }, + "generic-list-modal": { + "close": "{{common.close}}", + "clear": "Průhledná", + "filter": "Filtr", + "open-filtered-search": "Otevřít filtrované vyhledávání pro {{item}}" + }, + "user-stats-info-cards": { + "total-pages-read-label": "Celkový počet přečtených stránek", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", + "total-words-read-label": "Celkem přečtených slov", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "Čas strávený čtením", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "Přečtené kapitoly", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "avg-reading-per-week-label": "Průměrné čtení / týden", + "last-active-label": "Poslední aktivní", + "chapters": "" + }, + "user-stats": { + "library-read-progress-title": "", + "read-percentage": "" + }, + "top-readers": { + "title": "", + "time-selection-label": "", + "comics-label": "", + "manga-label": "", + "books-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "role-selector": { + "title": "" + }, + "directory-picker": { + "title": "", + "close": "", + "path-label": "", + "path-placeholder": "", + "instructions": "", + "type-header": "", + "name-header": "", + "cancel": "", + "share": "", + "help": "" + }, + "library-access-modal": { + "select-all": "", + "deselect-all": "", + "title": "", + "close": "", + "reset": "", + "cancel": "", + "save": "", + "no-data": "" + }, + "time-periods": { + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "device-platform-pipe": { + "custom": "" + }, + "day-of-week-pipe": { + "monday": "", + "tuesday": "", + "wednesday": "", + "thursday": "", + "friday": "", + "saturday": "", + "sunday": "" + }, + "cbl-import-result-pipe": { + "success": "", + "partial": "", + "failure": "" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "", + "chapter-missing": "", + "empty-file": "", + "name-conflict": "", + "series-collision": "", + "series-missing": "", + "volume-missing": "", + "all-chapter-missing": "", + "invalid-file": "", + "success": "" + }, + "time-duration-pipe": { + "hours": "", + "minutes": "", + "days": "", + "months": "", + "years": "" + }, + "time-ago-pipe": { + "never": "", + "just-now": "", + "min-ago": "", + "mins-ago": "", + "hour-ago": "", + "hours-ago": "", + "day-ago": "", + "days-ago": "", + "month-ago": "", + "months-ago": "", + "year-ago": "", + "years-ago": "" + }, + "relationship-pipe": { + "adaptation": "", + "alternative-setting": "", + "alternative-version": "", + "character": "", + "contains": "", + "doujinshi": "", + "other": "", + "prequel": "", + "sequel": "", + "side-story": "", + "spin-off": "", + "parent": "", + "edition": "" + }, + "publication-status-pipe": { + "ongoing": "", + "hiatus": "", + "completed": "", + "cancelled": "", + "ended": "" + }, + "person-role-pipe": { + "artist": "", + "character": "", + "colorist": "", + "cover-artist": "", + "editor": "", + "inker": "", + "letterer": "", + "penciller": "", + "publisher": "", + "writer": "", + "other": "" + }, + "manga-format-pipe": { + "epub": "", + "archive": "", + "image": "", + "pdf": "", + "unknown": "" + }, + "library-type-pipe": { + "book": "", + "comic": "", + "manga": "" + }, + "age-rating-pipe": { + "unknown": "", + "early-childhood": "", + "adults-only": "", + "everyone": "", + "everyone-10-plus": "", + "g": "", + "kids-to-adults": "", + "mature": "", + "ma15-plus": "", + "mature-17-plus": "", + "rating-pending": "", + "teen": "", + "x18-plus": "", + "not-applicable": "", + "pg": "", + "r18-plus": "" + }, + "reset-password": { + "title": "", + "description": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "submit": "" + }, + "reset-password-modal": { + "title": "", + "new-password-label": "", + "error-label": "", + "close": "", + "cancel": "", + "save": "" + }, + "all-series": { + "title": "", + "series-count": "" + }, + "announcements": { + "title": "" + }, + "changelog": { + "installed": "", + "download": "", + "published-label": "", + "available": "", + "description": "", + "description-continued": "" + }, + "invite-user": { + "title": "", + "close": "", + "description": "", + "email": "", + "required-field": "", + "setup-user-title": "", + "setup-user-description": "", + "setup-user-account": "", + "setup-user-account-tooltip": "", + "invite-url-label": "", + "invite": "", + "inviting": "", + "cancel": "" + }, + "library-selector": { + "title": "", + "select-all": "", + "deselect-all": "", + "no-data": "" + }, + "license": { + "title": "", + "manage": "", + "invalid-license-tooltip": "", + "check": "", + "cancel": "", + "edit": "", + "buy": "", + "activate": "", + "renew": "", + "no-license-key": "", + "license-valid": "", + "license-not-valid": "", + "loading": "", + "activate-description": "", + "activate-license-label": "", + "activate-email-label": "", + "activate-delete": "", + "activate-save": "" + }, + "book-line-overlay": { + "copy": "", + "bookmark": "", + "close": "", + "required-field": "", + "bookmark-label": "", + "save": "" + }, + "book-reader": { + "title": "", + "page-label": "", + "pagination-header": "", + "go-to-page": "", + "go-to-last-page": "", + "prev-page": "", + "next-page": "", + "prev-chapter": "", + "next-chapter": "", + "skip-header": "", + "virtual-pages": "", + "settings-header": "", + "table-of-contents-header": "", + "bookmarks-header": "", + "toc-header": "", + "loading-book": "", + "go-back": "", + "incognito-mode-alt": "", + "incognito-mode-label": "", + "next": "", + "previous": "", + "go-to-page-prompt": "" + }, + "personal-table-of-contents": { + "no-data": "", + "page": "", + "delete": "" + }, + "confirm-email": { + "title": "", + "description": "", + "error-label": "", + "username-label": "", + "password-label": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "confirm-email-change": { + "title": "", + "non-confirm-description": "", + "confirm-description": "", + "success": "" + }, + "confirm-reset-password": { + "title": "", + "description": "", + "password-label": "", + "required-field": "", + "submit": "", + "password-validation": "" + }, + "register": { + "title": "", + "description": "", + "username-label": "", + "email-label": "", + "email-tooltip": "", + "password-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "series-detail": { + "page-settings-title": "", + "close": "", + "layout-mode-label": "", + "layout-mode-option-card": "", + "layout-mode-option-list": "", + "continue-from": "", + "read": "", + "continue": "", + "read-options-alt": "", + "incognito": "", + "remove-from-want-to-read": "", + "add-to-want-to-read": "", + "edit-series-alt": "", + "download-series--tooltip": "", + "downloading-status": "", + "user-reviews-alt": "", + "storyline-tab": "", + "books-tab": "", + "volumes-tab": "", + "specials-tab": "", + "related-tab": "", + "recommendations-tab": "", + "send-to": "", + "no-pages": "", + "no-chapters": "", + "cover-change": "" + }, + "series-metadata-detail": { + "links-title": "", + "genres-title": "", + "tags-title": "", + "collections-title": "", + "reading-lists-title": "", + "writers-title": "", + "cover-artists-title": "", + "characters-title": "", + "colorists-title": "", + "editors-title": "", + "inkers-title": "", + "letterers-title": "", + "translators-title": "", + "pencillers-title": "", + "publishers-title": "", + "promoted": "", + "see-more": "", + "see-less": "" + }, + "badge-expander": { + "more-items": "" + }, + "read-more": { + "read-more": "", + "read-less": "" + }, + "update-notification-modal": { + "title": "", + "close": "", + "help": "", + "download": "" + }, + "side-nav-companion-bar": { + "page-settings-title": "", + "open-filter-and-sort": "", + "close-filter-and-sort": "", + "filter-and-sort-alt": "" + }, + "side-nav": { + "home": "", + "want-to-read": "", + "collections": "", + "reading-lists": "", + "bookmarks": "", + "filter-label": "", + "all-series": "", + "clear": "", + "donate": "" + }, + "library-settings-modal": { + "close": "", + "edit-title": "", + "add-title": "", + "general-tab": "", + "folder-tab": "", + "cover-tab": "", + "advanced-tab": "", + "name-label": "", + "library-name-unique": "", + "last-scanned-label": "", + "type-label": "", + "type-tooltip": "", + "folder-description": "", + "browse": "", + "help-us-part-1": "", + "help-us-part-2": "", + "help-us-part-3": "", + "naming-conventions-part-1": "", + "naming-conventions-part-2": "", + "naming-conventions-part-3": "", + "cover-description": "", + "cover-description-extra": "", + "manage-collection-label": "", + "manage-collection-tooltip": "", + "manage-reading-list-label": "", + "manage-reading-list-tooltip": "", + "allow-scrobbling-label": "", + "allow-scrobbling-tooltip": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "include-in-dashboard-label": "", + "include-in-dashboard-tooltip": "", + "include-in-recommendation-label": "", + "include-in-recommendation-tooltip": "", + "include-in-search-label": "", + "include-in-search-tooltip": "", + "force-scan": "", + "force-scan-tooltip": "", + "reset": "", + "cancel": "", + "next": "", + "save": "", + "required-field": "" + }, + "reader-settings": { + "general-settings-title": "", + "font-family-label": "", + "font-size-label": "", + "line-spacing-label": "", + "margin-label": "", + "reset-to-defaults": "", + "reader-settings-title": "", + "reading-direction-label": "", + "right-to-left": "", + "left-to-right": "", + "horizontal": "", + "vertical": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "on": "", + "off": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "fullscreen-label": "", + "fullscreen-tooltip": "", + "exit": "", + "enter": "", + "layout-mode-label": "", + "layout-mode-tooltip": "", + "layout-mode-option-scroll": "", + "layout-mode-option-1col": "", + "layout-mode-option-2col": "", + "color-theme-title": "", + "theme-dark": "", + "theme-black": "", + "theme-white": "", + "theme-paper": "" + }, + "table-of-contents": { + "no-data": "" + }, + "bookmarks": { + "title": "", + "series-count": "", + "no-data": "", + "no-data-2": "", + "confirm-delete": "", + "confirm-single-delete": "", + "delete-success": "", + "delete-single-success": "" + }, + "bulk-operations": { + "title": "", + "items-selected": "", + "mark-as-unread": "", + "mark-as-read": "", + "deselect-all": "" + }, + "card-detail-drawer": { + "general-tab": "", + "metadata-tab": "", + "cover-tab": "", + "info-tab": "", + "no-summary": "", + "writers-title": "", + "genres-title": "", + "publishers-title": "", + "tags-title": "", + "not-defined": "", + "read": "", + "unread": "", + "files": "", + "pages": "", + "added": "", + "size": "" + }, + "card-detail-layout": { + "total-items": "" + }, + "card-item": { + "cannot-read": "" + }, + "chapter-metadata-detail": { + "no-data": "", + "writers-title": "", + "publishers-title": "", + "characters-title": "", + "translators-title": "", + "letterers-title": "", + "colorists-title": "", + "inkers-title": "", + "pencillers-title": "", + "cover-artists-title": "", + "editors-title": "" + }, + "cover-image-chooser": { + "drag-n-drop": "", + "upload": "", + "upload-continued": "", + "url-label": "", + "load": "", + "back": "", + "reset-cover-tooltip": "", + "reset": "", + "image-num": "", + "apply": "", + "applied": "" + }, + "download-indicator": { + "progress": "" + }, + "edit-series-relation": { + "description-part-1": "", + "description-part-2": "", + "target-series": "", + "relationship": "", + "remove": "", + "add-relationship": "", + "parent": "" + }, + "entity-info-cards": { + "tags-title": "", + "characters-title": "", + "release-date-title": "", + "release-date-tooltip": "", + "age-rating-title": "", + "length-title": "", + "pages-count": "", + "words-count": "", + "reading-time-title": "", + "date-added-title": "", + "size-title": "", + "id-title": "", + "links-title": "", + "isbn-title": "", + "last-read-title": "", + "less-than-hour": "", + "range-hours": "", + "hour": "", + "hours": "", + "read-time-title": "" + }, + "series-info-cards": { + "release-date-title": "", + "release-year-tooltip": "", + "age-rating-title": "", + "language-title": "", + "publication-status-title": "", + "publication-status-tooltip": "", + "scrobbling-title": "", + "scrobbling-tooltip": "", + "on": "", + "off": "", + "disabled": "", + "format-title": "", + "last-read-title": "", + "length-title": "", + "read-time-title": "", + "less-than-hour": "", + "hour": "", + "hours": "", + "time-left-title": "", + "ongoing": "", + "pages-count": "", + "words-count": "" + }, + "bulk-add-to-collection": { + "title": "", + "promoted": "", + "close": "", + "filter-label": "", + "clear": "", + "no-data": "", + "loading": "", + "collection-label": "", + "create": "" + }, + "entity-title": { + "special": "", + "issue-num": "", + "chapter": "" + }, + "external-series-card": { + "open-external": "" + }, + "list-item": { + "read": "" + }, + "manage-alerts": { + "description-part-1": "", + "description-part-2": "", + "filter-label": "", + "clear-alerts": "", + "extension-header": "", + "file-header": "", + "comment-header": "", + "details-header": "" + }, + "manage-email-settings": { + "title": "", + "description": "", + "send-to-warning": "", + "email-url-label": "", + "email-url-tooltip": "", + "reset": "", + "test": "", + "host-name-label": "", + "host-name-tooltip": "", + "host-name-validation": "", + "reset-to-default": "", + "save": "" + }, + "manage-library": { + "title": "", + "add-library": "", + "no-data": "", + "loading": "", + "last-scanned-title": "", + "shared-folders-title": "", + "type-title": "", + "scan-library": "", + "delete-library": "", + "delete-library-by-name": "", + "edit-library": "", + "edit-library-by-name": "" + }, + "manage-media-settings": { + "encode-as-description-part-1": "", + "encode-as-description-part-2": "", + "encode-as-description-part-3": "", + "encode-as-warning": "", + "media-warning": "", + "encode-as-label": "", + "encode-as-tooltip": "", + "bookmark-dir-label": "", + "bookmark-dir-tooltip": "", + "change": "", + "reset-to-default": "", + "reset": "", + "save": "", + "media-issue-title": "", + "scrobble-issue-title": "", + "cover-image-size-label": "", + "cover-image-size-tooltip": "" + }, + "cover-image-size": { + "default": "", + "medium": "", + "large": "", + "xlarge": "" + }, + "manage-scrobble-errors": { + "description": "", + "filter-label": "", + "clear-errors": "", + "series-header": "", + "created-header": "", + "comment-header": "", + "edit-header": "", + "edit-item-alt": "" + }, + "default-date-pipe": { + "never": "" + }, + "manage-settings": { + "notice": "", + "restart-required": "", + "base-url-label": "", + "base-url-tooltip": "", + "ip-address-label": "", + "ip-address-tooltip": "", + "port-label": "", + "port-tooltip": "", + "backup-label": "", + "backup-tooltip": "", + "log-label": "", + "log-tooltip": "", + "logging-level-label": "", + "logging-level-tooltip": "", + "cache-size-label": "", + "cache-size-tooltip": "", + "on-deck-last-progress-label": "", + "on-deck-last-progress-tooltip": "", + "on-deck-last-chapter-add-label": "", + "on-deck-last-chapter-add-tooltip": "", + "allow-stats-label": "", + "allow-stats-tooltip-part-1": "", + "allow-stats-tooltip-part-2": "", + "send-data": "", + "opds-label": "", + "opds-tooltip": "", + "enable-opds": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "enable-folder-watching": "", + "reset-to-default": "", + "reset": "", + "save": "", + "cache-size-validation": "", + "field-required": "", + "max-logs-validation": "", + "min-logs-validation": "", + "min-days-validation": "", + "min-cache-validation": "", + "max-backup-validation": "", + "min-backup-validation": "", + "ip-address-validation": "", + "base-url-validation": "" + }, + "manage-system": { + "title": "", + "version-title": "", + "installId-title": "", + "more-info-title": "", + "home-page-title": "", + "wiki-title": "", + "discord-title": "", + "donations-title": "", + "source-title": "", + "feature-request-title": "" + }, + "manage-tasks-settings": { + "title": "", + "library-scan-label": "", + "library-scan-tooltip": "", + "library-database-backup-label": "", + "library-database-backup-tooltip": "", + "adhoc-tasks-title": "", + "job-title-header": "", + "description-header": "", + "action-header": "", + "reset-to-default": "", + "reset": "", + "save": "", + "recurring-tasks-title": "", + "last-executed-header": "", + "cron-header": "", + "convert-media-task": "", + "convert-media-task-desc": "", + "convert-media-success": "", + "bust-cache-task": "", + "bust-cache-task-desc": "", + "bust-cache-task-success": "", + "clear-reading-cache-task": "", + "clear-reading-cache-task-desc": "", + "clear-reading-cache-task-success": "", + "clean-up-want-to-read-task": "", + "clean-up-want-to-read-task-desc": "", + "clean-up-want-to-read-task-success": "", + "backup-database-task": "", + "backup-database-task-desc": "", + "backup-database-task-success": "", + "download-logs-task": "", + "download-logs-task-desc": "", + "analyze-files-task": "", + "analyze-files-task-desc": "", + "analyze-files-task-success": "", + "check-for-updates-task": "", + "check-for-updates-task-desc": "" + }, + "manage-users": { + "title": "", + "invite": "", + "you-alt": "", + "pending-title": "", + "delete-user-tooltip": "", + "delete-user-alt": "", + "edit-user-tooltip": "", + "edit-user-alt": "", + "resend-invite-tooltip": "", + "resend-invite-alt": "", + "setup-user-tooltip": "", + "setup-user-alt": "", + "change-password-tooltip": "", + "change-password-alt": "", + "resend": "", + "setup": "", + "last-active-title": "", + "roles-title": "", + "none": "", + "never": "", + "online-now-tooltip": "", + "sharing-title": "", + "no-data": "", + "loading": "" + }, + "edit-collection-tags": { + "title": "", + "required-field": "", + "save": "", + "close": "", + "cancel": "", + "general-tab": "", + "cover-image-tab": "", + "series-tab": "", + "name-label": "", + "name-validation": "", + "promote-label": "", + "promote-tooltip": "", + "summary-label": "", + "series-title": "", + "deselect-all": "", + "select-all": "" + }, + "library-detail": { + "library-tab": "", + "recommended-tab": "" + }, + "library-recommended": { + "no-data": "", + "more-in-genre": "", + "rediscover": "", + "highly-rated": "", + "quick-catchups": "", + "quick-reads": "", + "on-deck": "" + }, + "admin-dashboard": { + "title": "", + "general-tab": "", + "users-tab": "", + "libraries-tab": "", + "media-tab": "", + "logs-tab": "", + "email-tab": "", + "tasks-tab": "", + "statistics-tab": "", + "system-tab": "", + "kavita+-tab": "", + "kavita+-desc-part-1": "", + "kavita+-desc-part-2": "", + "kavita+-desc-part-3": "" + }, + "collection-detail": { + "no-data": "", + "no-data-filtered": "", + "title-alt": "" + }, + "all-collections": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "carousel-reel": { + "prev-items": "", + "next-items": "" + }, + "draggable-ordered-list": { + "instructions-alt": "", + "reorder-label": "", + "remove-item-alt": "" + }, + "reading-lists": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "reading-list-item": { + "remove": "", + "read": "" + }, + "reading-list-detail": { + "item-count": "", + "page-settings-title": "", + "remove-read": "", + "order-numbers-label": "", + "continue": "", + "read": "", + "read-options-alt": "", + "incognito-alt": "", + "no-data": "", + "characters-title": "" + }, + "events-widget": { + "title-alt": "", + "dismiss-all": "", + "update-available": "", + "downloading-item": "", + "more-info": "", + "close": "", + "users-online-count": "", + "active-events-title": "", + "no-data": "" + }, + "shortcuts-modal": { + "title": "", + "close": "", + "prev-page": "", + "next-page": "", + "go-to": "", + "bookmark": "", + "double-click": "", + "close-reader": "", + "toggle-menu": "" + }, + "grouped-typeahead": { + "files": "", + "chapters": "", + "people": "", + "tags": "", + "genres": "", + "libraries": "", + "reading-lists": "", + "collections": "", + "close": "", + "loading": "" + }, + "nav-header": { + "skip-alt": "", + "search-series-alt": "", + "search-alt": "", + "promoted": "", + "no-data": "", + "scroll-to-top-alt": "", + "server-settings": "", + "settings": "", + "help": "", + "announcements": "", + "logout": "" + }, + "add-to-list-modal": { + "title": "", + "close": "", + "filter-label": "", + "promoted-alt": "", + "no-data": "", + "loading": "", + "reading-list-label": "", + "create": "" + }, + "edit-reading-list-modal": { + "title": "", + "general-tab": "", + "cover-image-tab": "", + "close": "", + "save": "", + "year-validation": "", + "month-validation": "", + "name-unique-validation": "", + "required-field": "", + "summary-label": "", + "year-label": "", + "month-label": "", + "ending-title": "", + "starting-title": "", + "promote-label": "", + "promote-tooltip": "" + }, + "import-cbl-modal": { + "close": "", + "title": "", + "import-description": "", + "validate-description": "", + "validate-warning": "", + "validate-no-issue": "", + "validate-no-issue-description": "", + "dry-run-description": "", + "prev": "", + "import": "", + "restart": "", + "next": "", + "import-step": "", + "validate-cbl-step": "", + "dry-run-step": "", + "final-import-step": "" + }, + "pdf-reader": { + "loading-message": "", + "incognito-mode": "", + "light-theme-alt": "", + "dark-theme-alt": "", + "close-reader-alt": "" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "", + "continuous-reading-prev-chapter": "", + "continuous-reading-next-chapter-alt": "", + "continuous-reading-next-chapter": "" + }, + "manga-reader": { + "back": "", + "save-globally": "", + "incognito-alt": "", + "incognito-title": "", + "shortcuts-menu-alt": "", + "prev-page-tooltip": "", + "next-page-tooltip": "", + "prev-chapter-tooltip": "", + "next-chapter-tooltip": "", + "first-page-tooltip": "", + "last-page-tooltip": "", + "left-to-right-alt": "", + "right-to-left-alt": "", + "reading-direction-tooltip": "", + "reading-mode-tooltip": "", + "collapse": "", + "fullscreen": "", + "settings-tooltip": "", + "image-splitting-label": "", + "image-scaling-label": "", + "height": "", + "width": "", + "original": "", + "auto-close-menu-label": "", + "swipe-enabled-label": "", + "enable-comic-book-label": "", + "brightness-label": "", + "first-time-reading-manga": "", + "layout-mode-switched": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "user-preferences-updated": "", + "emulate-comic-book-label": "" + }, + "metadata-filter": { + "filter-title": "", + "sort-by-label": "", + "ascending-alt": "", + "descending-alt": "", + "reset": "", + "apply": "", + "limit-label": "", + "format-label": "", + "libraries-label": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "read-progress-label": "", + "unread": "", + "read": "", + "in-progress": "", + "rating-label": "", + "age-rating-label": "", + "language-label": "", + "publication-status-label": "", + "series-name-label": "", + "series-name-tooltip": "", + "release-label": "", + "min": "", + "max": "" + }, + "sort-field-pipe": { + "sort-name": "", + "created": "", + "last-modified": "", + "last-chapter-added": "", + "time-to-read": "", + "release-year": "" + }, + "edit-series-modal": { + "title": "", + "general-tab": "", + "metadata-tab": "", + "people-tab": "", + "web-links-tab": "", + "cover-image-tab": "", + "related-tab": "", + "info-tab": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "language-label": "", + "age-rating-label": "", + "publication-status-label": "", + "required-field": "", + "close": "", + "name-label": "", + "sort-name-label": "", + "localized-name-label": "", + "summary-label": "", + "release-year-label": "", + "web-link-description": "", + "web-link-label": "", + "add-link-alt": "", + "remove-link-alt": "", + "cover-image-description": "", + "save": "", + "field-locked-alt": "", + "info-title": "", + "library-title": "", + "format-title": "", + "created-title": "", + "last-read-title": "", + "last-added-title": "", + "last-scanned-title": "", + "folder-path-title": "", + "publication-status-title": "", + "total-pages-title": "", + "total-items-title": "", + "max-items-title": "", + "size-title": "", + "loading": "", + "added-title": "", + "last-modified-title": "", + "view-files": "", + "pages-title": "", + "chapter-title": "", + "volume-num": "", + "highest-count-tooltip": "", + "max-issue-tooltip": "" + }, + "day-breakdown": { + "title": "", + "x-axis-label": "", + "y-axis-label": "" + }, + "file-breakdown-stats": { + "format-title": "", + "format-tooltip": "", + "visualisation-label": "", + "data-table-label": "", + "extension-header": "", + "format-header": "", + "total-size-header": "", + "total-files-header": "", + "not-classified": "", + "total-file-size-title": "" + }, + "reading-activity": { + "title": "", + "legend-label": "", + "x-axis-label": "", + "y-axis-label": "", + "no-data": "", + "time-frame-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "manga-format-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "format-header": "", + "count-header": "" + }, + "publication-status-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "year-header": "", + "count-header": "" + }, + "server-stats": { + "total-series-label": "", + "total-series-tooltip": "", + "total-volumes-label": "", + "total-volumes-tooltip": "", + "total-files-label": "", + "total-files-tooltip": "", + "total-size-label": "", + "total-genres-label": "", + "total-genres-tooltip": "", + "total-tags-label": "", + "total-tags-tooltip": "", + "total-people-label": "", + "total-people-tooltip": "", + "total-read-time-label": "", + "total-read-time-tooltip": "", + "series": "", + "reads": "", + "release-years-title": "", + "most-active-users-title": "", + "popular-libraries-title": "", + "popular-series-title": "", + "recently-read-title": "", + "genre-count": "", + "tag-count": "", + "people-count": "", + "tags": "", + "people": "", + "genres": "" + }, + "errors": { + "series-doesnt-exist": "", + "collection-invalid-access": "", + "unknown-crit": "", + "user-not-auth": "", + "error-code": "", + "download": "", + "not-found": "", + "generic": "", + "rejected-cover-upload": "", + "invalid-confirmation-url": "", + "invalid-confirmation-email": "", + "invalid-password-reset-url": "" + }, + "metadata-builder": { + "or": "", + "and": "", + "add-rule": "", + "remove-rule": "" + }, + "filter-field-pipe": { + "age-rating": "", + "characters": "", + "collection-tags": "", + "colorist": "", + "cover-artist": "", + "editor": "", + "formats": "", + "genres": "", + "inker": "", + "languages": "", + "libraries": "", + "letterer": "", + "publication-status": "", + "penciller": "", + "publisher": "", + "read-progress": "", + "read-time": "", + "release-year": "", + "series-name": "", + "summary": "", + "tags": "", + "translators": "", + "user-rating": "", + "writers": "" + }, + "filter-comparison-pipe": { + "begins-with": "", + "contains": "", + "equal": "", + "greater-than": "", + "greater-than-or-equal": "", + "less-than": "", + "less-than-or-equal": "", + "matches": "", + "does-not-contain": "", + "not-equal": "", + "ends-with": "", + "is-before": "", + "is-after": "", + "is-in-last": "", + "is-not-in-last": "" + }, + "toasts": { + "regen-cover": "", + "no-pages": "", + "download-in-progress": "", + "scan-queued": "", + "server-settings-updated": "", + "reset-ip-address": "", + "reset-base-url": "", + "unauthorized-1": "", + "unauthorized-2": "", + "no-updates": "", + "confirm-delete-user": "", + "user-deleted": "", + "email-sent-to-user": "", + "click-email-link": "", + "series-added-to-collection": "", + "no-series-collection-warning": "", + "collection-updated": "", + "reading-list-deleted": "", + "reading-list-updated": "", + "confirm-delete-reading-list": "", + "item-removed": "", + "nothing-to-remove": "", + "series-added-to-reading-list": "", + "volumes-added-to-reading-list": "", + "chapter-added-to-reading-list": "", + "multiple-added-to-reading-list": "", + "select-files-warning": "", + "reading-list-imported": "", + "incognito-off": "", + "email-service-reset": "", + "email-service-reachable": "", + "email-service-unresponsive": "", + "refresh-covers-queued": "", + "library-file-analysis-queued": "", + "entity-read": "", + "entity-unread": "", + "mark-read": "", + "mark-unread": "", + "series-removed-want-to-read": "", + "series-deleted": "", + "file-send-to": "", + "theme-missing": "", + "email-sent": "", + "k+-license-saved": "", + "k+-unlocked": "", + "k+-error": "", + "k+-delete-key": "", + "library-deleted": "", + "copied-to-clipboard": "", + "book-settings-info": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "load-next-chapter": "", + "load-prev-chapter": "", + "account-registration-complete": "", + "account-migration-complete": "", + "password-reset": "", + "password-updated": "", + "forced-scan-queued": "", + "library-created": "", + "anilist-token-updated": "", + "age-restriction-updated": "", + "email-sent-to-no-existing": "", + "email-sent-to": "", + "change-email-private": "", + "device-updated": "", + "device-created": "", + "confirm-regen-covers": "", + "alert-long-running": "", + "confirm-delete-multiple-series": "", + "confirm-delete-series": "", + "alert-bad-theme": "", + "confirm-library-delete": "", + "confirm-library-type-change": "", + "confirm-download-size": "", + "list-doesnt-exist": "" + }, + "actionable": { + "scan-library": "", + "refresh-covers": "", + "analyze-files": "", + "settings": "", + "edit": "", + "mark-as-read": "", + "mark-as-unread": "", + "scan-series": "", + "add-to": "", + "add-to-want-to-read": "", + "remove-from-want-to-read": "", + "remove-from-on-deck": "", + "others": "", + "add-to-reading-list": "", + "add-to-collection": "", + "send-to": "", + "delete": "", + "download": "", + "read-incognito": "", + "details": "", + "view-series": "", + "clear": "", + "import-cbl": "", + "read": "", + "add-rule-group-and": "", + "add-rule-group-or": "", + "remove-rule-group": "" + }, + "preferences": { + "left-to-right": "", + "right-to-left": "", + "horizontal": "", + "vertical": "", + "automatic": "", + "fit-to-height": "", + "fit-to-width": "", + "original": "", + "fit-to-screen": "", + "no-split": "", + "webtoon": "", + "single": "", + "double": "", + "double-manga": "", + "scroll": "", + "1-column": "", + "2-column": "", + "cards": "", + "list": "", + "up-to-down": "" + }, + "validation": { + "required-field": "", + "valid-email": "", + "password-validation": "" + }, + "entity-type": { + "volume": "", + "chapter": "", + "series": "", + "bookmark": "", + "logs": "" + }, + "common": { + "reset-to-default": "", + "close": "", + "cancel": "", + "create": "", + "save": "", + "reset": "", + "add": "", + "apply": "", + "delete": "", + "edit": "", + "help": "", + "submit": "", + "email": "", + "read": "", + "loading": "", + "username": "", + "password": "", + "promoted": "", + "select-all": "", + "deselect-all": "", + "series-count": "", + "item-count": "", + "book-num": "", + "issue-hash-num": "", + "issue-num": "", + "chapter-num": "", + "volume-num": "" + } +} diff --git a/UI/Web/src/assets/langs/de.json b/UI/Web/src/assets/langs/de.json index fc787d7196..9757058aad 100644 --- a/UI/Web/src/assets/langs/de.json +++ b/UI/Web/src/assets/langs/de.json @@ -55,7 +55,7 @@ }, "review-series-modal": { "title": "Rezension bearbeiten", - "tagline-label": "", + "tagline-label": "Schlagzeile", "review-label": "Rezension", "close": "{{common.close}}", "save": "{{common.save}}" @@ -87,7 +87,7 @@ "theme-tab": "Motiv", "devices-tab": "Geräte", "stats-tab": "Statistiken", - "scrobbling-tab": "Scrobbling", + "scrobbling-tab": "Kritzeln", "success-toast": "Benutzerpräferenzen aktualisiert", "global-settings-title": "Globale Einstellungen", "page-layout-mode-label": "Seitenlayoutmodus", @@ -137,7 +137,7 @@ "font-size-book-label": "Schriftgröße", "line-height-book-label": "Zeilenabstände", "line-height-book-tooltip": "Wie viel Abstand zwischen den Zeilen im Buch", - "margin-book-label": "", + "margin-book-label": "Rahmen", "margin-book-tooltip": "Wie viel Abstand auf jeder Seite des Bildschirms. Auf mobilen Geräten wird dieser Wert unabhängig von dieser Einstellung auf 0 gesetzt.", "clients-opds-alert": "OPDS ist auf diesem Server nicht aktiviert. Tachiyomi-Benutzer sind davon nicht betroffen.", "clients-opds-description": "Alle Clienten von Drittanbietern verwenden entweder den API-Schlüssel oder die unten stehende Verbindungsurl. Diese sind wie Passwörter, vertraulich behandeln.", @@ -169,61 +169,61 @@ "theme-white": "weiß" }, "restriction-selector": { - "title": "", - "description": "", - "not-applicable-for-admins": "", - "age-rating-label": "", + "title": "Altersbeschränkung", + "description": "Wenn diese Option ausgewählt ist, werden alle Serien und Leselisten die mindestens einen Eintrag enthalten, der größer als die ausgewählte Beschränkung ist, aus den Suchergebnissen entfernt.", + "not-applicable-for-admins": "Admins sind davon nicht betroffen.", + "age-rating-label": "Altersfreigabe", "no-restriction": "Keine Einschränkung", "include-unknowns-label": "Unbekannte einbeziehen", - "include-unknowns-tooltip": "" + "include-unknowns-tooltip": "Falls wahr, werden unbekannte Medien mit Altersbeschränkung zugelassen. Dies könnte dazu führen, dass nicht gekennzeichnete Medien an Benutzer mit Altersbeschränkungen durchsickern." }, "site-theme-provider-pipe": { - "system": "", + "system": "System", "user": "Benutzer" }, "manage-devices": { - "title": "", + "title": "Gerätemanager", "description": "", - "devices-title": "", - "no-devices": "", - "platform-label": "", - "email-label": "", + "devices-title": "Geräte", + "no-devices": "Es ist noch kein Gerät eingerichtet", + "platform-label": "Plattform: ", + "email-label": "E-Mail: ", "add": "{{common.add}}", "delete": "{{common.delete}}", "edit": "{{common.edit}}" }, "edit-device": { - "device-name-label": "", + "device-name-label": "Gerätename", "email-label": "{{common.email}}", - "email-tooltip": "", - "device-platform-label": "", + "email-tooltip": "Diese E-Mail wird verwendet, um die Datei über \"Senden an\" anzunehmen", + "device-platform-label": "Geräteplattform", "save": "{{common.save}}", "required-field": "{{validation.required-field}}", "valid-email": "{{validation.valid-email}}" }, "change-password": { "password-label": "{{common.password}}", - "current-password-label": "", - "new-password-label": "", - "confirm-password-label": "", + "current-password-label": "Aktuelles Passwort", + "new-password-label": "Neues Passwort", + "confirm-password-label": "Passwort bestätigen", "reset": "{{common.reset}}", "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", "save": "{{common.save}}", "required-field": "{{validation.required-field}}", - "passwords-must-match": "", - "permission-error": "" + "passwords-must-match": "Passwörter müssen übereinstimmen", + "permission-error": "Sie haben nicht die Erlaubnis, Ihr Passwort zu ändern. Wenden Sie sich an den Administrator des Servers." }, "change-email": { "email-label": "{{common.email}}", - "current-password-label": "", - "email-not-confirmed": "", - "email-updated-title": "", + "current-password-label": "Aktuelles Passwort", + "email-not-confirmed": "Diese E-Mail-Adresse ist nicht bestätigt", + "email-updated-title": "E-Mail Adresse aktualisiert", "email-updated-description": "", - "setup-user-account": "", + "setup-user-account": "User Account einrichten", "invite-url-label": "", - "invite-url-tooltip": "", - "permission-error": "", + "invite-url-tooltip": "Kopieren Sie dies und fügen Sie es in einem neuen Tab ein", + "permission-error": "Sie haben keine Berechtigung ihre Email Addresse zu ändern. Bitte wenden Sie sich an den Admin des Servers.", "required-field": "{{validation.required-field}}", "reset": "{{common.reset}}", "edit": "{{common.edit}}", @@ -231,27 +231,27 @@ "save": "{{common.save}}" }, "change-age-restriction": { - "age-restriction-label": "", - "unknowns": "", + "age-restriction-label": "Altersbeschränkung", + "unknowns": "Unbekannt", "reset": "{{common.reset}}", "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", "save": "{{common.save}}" }, "api-key": { - "copy": "", + "copy": "Kopie", "regen-warning": "", - "no-key": "", + "no-key": "ERROR - SCHLÜSSEL NICHT GESETZT", "confirm-reset": "", - "key-reset": "" + "key-reset": "API Schlüssel zurückgesetzt" }, "scrobbling-providers": { "title": "", - "requires": "", + "requires": "Diese Funktion benötigt eine aktive {{product}} Lizenz", "token-expired": "", "no-token-set": "", "token-set": "", - "generate": "", + "generate": "Generieren", "instructions": "", "token-input-label": "", "edit": "{{common.edit}}", @@ -259,7 +259,7 @@ "save": "{{common.save}}" }, "typeahead": { - "locked-field": "", + "locked-field": "Feld ist gesperrt", "close": "{{common.close}}", "loading": "{{common.loading}}", "add-item": "", @@ -486,7 +486,7 @@ "email": "{{common.email}}", "required-field": "{{common.required-field}}", "setup-user-title": "", - "setup-user-description": "", + "setup-user-description": "Sie können den folgenden Link verwenden, um das Konto für Ihren Benutzer einzurichten, oder die Taste \"Kopieren\" verwenden. Möglicherweise müssen Sie sich erst abmelden, bevor Sie den Link zur Registrierung eines neuen Benutzers verwenden können. Wenn Ihr Server von außen erreichbar ist, wurde eine E-Mail an den Benutzer gesendet und die Links können von diesem verwendet werden, um die Einrichtung des Kontos abzuschließen.", "setup-user-account": "", "setup-user-account-tooltip": "", "invite-url-label": "", @@ -514,8 +514,8 @@ "license-valid": "", "license-not-valid": "", "loading": "{{common.loading}}", - "activate-description": "", - "activate-license-label": "", + "activate-description": "Geben Sie den Lizenzschlüssel und die E-Mail-Adresse ein, die Sie bei der Registrierung bei Stripe verwendet haben", + "activate-license-label": "Lizenzschlüssel", "activate-email-label": "{{common.email}}", "activate-delete": "Löschen", "activate-save": "{{common.save}}" @@ -535,30 +535,31 @@ "go-to-page": "", "go-to-last-page": "", "prev-page": "", - "next-page": "", - "prev-chapter": "", - "next-chapter": "", - "skip-header": "", + "next-page": "Nächste Seite", + "prev-chapter": "Vorheriges Kapitel/Band", + "next-chapter": "Nächstes Kapitel/Band", + "skip-header": "Zum Hauptinhalt springen", "virtual-pages": "", "settings-header": "Einstellungen", - "table-of-contents-header": "", + "table-of-contents-header": "Inhaltsverzeichnis", "bookmarks-header": "Lesezeichen", "toc-header": "", - "loading-book": "", + "loading-book": "Buch wird geladen…", "go-back": "", "incognito-mode-alt": "", "incognito-mode-label": "", - "next": "", - "previous": "" + "next": "Nächste", + "previous": "Vorherige", + "go-to-page-prompt": "Es gibt {{totalPages}} Seiten. Auf welche Seite möchten sie gehen?" }, "personal-table-of-contents": { "no-data": "", - "page": "", - "delete": "" + "page": "Seite {{value}}", + "delete": "Löschen {{bookmarkName}}" }, "confirm-email": { - "title": "", - "description": "", + "title": "Registrieren", + "description": "Füllen Sie das Formular aus, um Ihre Registrierung abzuschließen", "error-label": "", "username-label": "{{common.username}}", "password-label": "{{common.password}}", @@ -569,13 +570,13 @@ "register": "" }, "confirm-email-change": { - "title": "", - "non-confirm-description": "", - "confirm-description": "", + "title": "E-Mail-Änderung bestätigen", + "non-confirm-description": "Bitte warten Sie, während Ihre E-Mail-Aktualisierung bestätigt wird.", + "confirm-description": "Ihre E-Mail wurde bestätigt und ist nun in Kavita geändert. Sie werden nun zur Anmeldung weitergeleitet.", "success": "" }, "confirm-reset-password": { - "title": "", + "title": "Passwort Zurücksetzen", "description": "", "password-label": "{{common.password}}", "required-field": "{{validation.required-field}}", @@ -583,11 +584,11 @@ "password-validation": "{{validation.password-validation}}" }, "register": { - "title": "", - "description": "", + "title": "Registrieren", + "description": "Füllen Sie das Formular aus, um ein Administratorkonto zu registrieren", "username-label": "{{common.username}}", "email-label": "{{common.email}}", - "email-tooltip": "", + "email-tooltip": "Die E-Mail muss keine echte Adresse sein, sondern ermöglicht den Zugriff auf vergesse Passwörter. Sie wird nicht außerhalb des Servers versendet, es sei denn, Passwort vergessen wird ohne einen benutzerdefinierten E-Mail-Service-Host verwendet.", "password-label": "{{common.password}}", "required-field": "{{validation.required-field}}", "valid-email": "{{validation.valid-email}}", @@ -595,37 +596,37 @@ "register": "" }, "series-detail": { - "page-settings-title": "", + "page-settings-title": "Seiteneinstellungen", "close": "{{common.close}}", "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", "layout-mode-option-card": "", "layout-mode-option-list": "", "continue-from": "", "read": "{{common.read}}", - "continue": "", + "continue": "Fortsetzen", "read-options-alt": "", "incognito": "", "remove-from-want-to-read": "", "add-to-want-to-read": "", "edit-series-alt": "", "download-series--tooltip": "", - "downloading-status": "", - "user-reviews-alt": "", + "downloading-status": "wird heruntergeladen…", + "user-reviews-alt": "Benutzerbewertungen", "storyline-tab": "", "books-tab": "Bücher", "volumes-tab": "Bände", - "specials-tab": "", - "related-tab": "", - "recommendations-tab": "", + "specials-tab": "Specials", + "related-tab": "Zugehörig", + "recommendations-tab": "Empfehlungen", "send-to": "", "no-pages": "{{toasts.no-pages}}", - "no-chapters": "", - "cover-change": "" + "no-chapters": "Zu diesem Band gibt es keine Kapitel. Kann nicht gelesen werden.", + "cover-change": "Es kann bis zu einer Minute dauern, bis Ihr Browser das Bild aktualisiert hat. Bis dahin kann auf einigen Seiten noch das alte Bild angezeigt werden." }, "series-metadata-detail": { - "links-title": "", - "genres-title": "", - "tags-title": "", + "links-title": "Links", + "genres-title": "Genres", + "tags-title": "Tags", "collections-title": "{{side-nav.collections}}", "reading-lists-title": "{{side-nav.reading-lists}}", "writers-title": "", @@ -646,11 +647,11 @@ "more-items": "" }, "read-more": { - "read-more": "", - "read-less": "" + "read-more": "Mehr lesen", + "read-less": "Weniger lesen" }, "update-notification-modal": { - "title": "", + "title": "Neue Version verfügbar!", "close": "{{common.close}}", "help": "", "download": "" @@ -667,7 +668,7 @@ "collections": "", "reading-lists": "", "bookmarks": "Lesezeichen", - "filter-label": "", + "filter-label": "Filter", "all-series": "", "clear": "", "donate": "Spenden" @@ -683,7 +684,7 @@ "name-label": "", "library-name-unique": "", "last-scanned-label": "", - "type-label": "", + "type-label": "Typ", "type-tooltip": "", "folder-description": "", "browse": "", @@ -697,8 +698,8 @@ "cover-description-extra": "", "manage-collection-label": "", "manage-collection-tooltip": "", - "manage-reading-list-label": "", - "manage-reading-list-tooltip": "", + "manage-reading-list-label": "Leselisten verwalten", + "manage-reading-list-tooltip": "Soll Kavita Leselisten aus den Tags StoryArc/StoryArcNumber und AlternativeSeries/AlternativeCount in den ComicInfo.xml/opf-Dateien erstellen", "allow-scrobbling-label": "", "allow-scrobbling-tooltip": "", "folder-watching-label": "", @@ -838,84 +839,84 @@ "entity-info-cards": { "tags-title": "{{series-metadata-detail.tags-title}}", "characters-title": "{{series-metadata-detail.characters-title}}", - "release-date-title": "", - "release-date-tooltip": "", - "age-rating-title": "", - "length-title": "", - "pages-count": "", - "words-count": "", - "reading-time-title": "", - "date-added-title": "", - "size-title": "", - "id-title": "", + "release-date-title": "Veröffentlichung", + "release-date-tooltip": "Erscheinungsdatum", + "age-rating-title": "Altersfreigabe", + "length-title": "Länge", + "pages-count": "{{num}} Seiten", + "words-count": "{{num}} Wörter", + "reading-time-title": "Lesezeit", + "date-added-title": "Hinzugefügt", + "size-title": "Größe", + "id-title": "ID", "links-title": "{{series-metadata-detail.links-title}}", - "isbn-title": "", - "last-read-title": "", - "less-than-hour": "", + "isbn-title": "ISBN", + "last-read-title": "Zuletzt gelesen", + "less-than-hour": "<1 Stunde", "range-hours": "{{value}} {{hourWord}}", - "hour": "", - "hours": "", + "hour": "Stunde", + "hours": "Stunden", "read-time-title": "{{series-info-cards.read-time-title}}" }, "series-info-cards": { "release-date-title": "{{entity-info-cards.release-date-title}}", - "release-year-tooltip": "", - "age-rating-title": "", - "language-title": "", - "publication-status-title": "", - "publication-status-tooltip": "", - "scrobbling-title": "", - "scrobbling-tooltip": "", - "on": "", - "off": "", - "disabled": "", - "format-title": "", - "last-read-title": "", - "length-title": "", - "read-time-title": "", - "less-than-hour": "", - "hour": "", - "hours": "", - "time-left-title": "", - "ongoing": "", - "pages-count": "", - "words-count": "" + "release-year-tooltip": "Erscheinungsjahr", + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "language-title": "Sprache", + "publication-status-title": "Veröffentlichung", + "publication-status-tooltip": "Status der Veröffentlichung", + "scrobbling-title": "Scrobbling", + "scrobbling-tooltip": "Scrobbling Status", + "on": "An", + "off": "Aus", + "disabled": "Deaktiviert", + "format-title": "Format", + "last-read-title": "Zuletzt gelesen", + "length-title": "Länge", + "read-time-title": "Lesezeit", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "time-left-title": "Verbleibende Zeit", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}" }, "bulk-add-to-collection": { - "title": "", - "promoted": "", - "close": "", - "filter-label": "", - "clear": "", - "no-data": "", - "loading": "", - "collection-label": "", - "create": "" + "title": "Zur Sammlung hinzufügen", + "promoted": "{{common.promoted}}", + "close": "{{common.close}}", + "filter-label": "Filter", + "clear": "{{common.clear}}", + "no-data": "Noch keine Sammlungen erstellt", + "loading": "{{common.loading}}", + "collection-label": "Sammlung", + "create": "{{common.create}}" }, "entity-title": { - "special": "", - "issue-num": "", - "chapter": "" + "special": "Spezial", + "issue-num": "Fehler #", + "chapter": "Kapitel" }, "external-series-card": { - "open-external": "" + "open-external": "Extern öffnen" }, "list-item": { "read": "{{common.read}}" }, "manage-alerts": { - "description-part-1": "", + "description-part-1": "Diese Tabelle enthält Fehler, die beim Scannen oder Einlesen Ihrer Medien gefunden wurden. Diese Liste ist nicht verwaltet. Sie können sie jederzeit löschen und mit Library (Erzwingen) Scan eine Analyse durchführen. Eine Liste mit einigen häufigen Fehlern und deren Bedeutung finden Sie unter der Seite ", "description-part-2": "Wiki.", - "filter-label": "", - "clear-alerts": "", - "extension-header": "", - "file-header": "", - "comment-header": "", - "details-header": "" + "filter-label": "Filter", + "clear-alerts": "Warnungen löschen", + "extension-header": "Erweiterung", + "file-header": "Datei", + "comment-header": "Kommentar", + "details-header": "Einzelheiten" }, "manage-email-settings": { - "title": "", - "description": "", + "title": "E-Mail-Dienste (SMTP)", + "description": "Kavita wird mit einem E-Mail-Service ausgeliefert, der Aufgaben wie das Einladen von Benutzern, das Zurücksetzen von Passwörtern usw. ermöglicht. E-Mails, die über unseren Dienst gesendet werden, werden sofort gelöscht. Sie können Ihren eigenen E-Mail-Dienst verwenden, indem Sie den {{link}} Dienst einrichten. Geben Sie die URL des E-Mail-Dienstes an und verwenden Sie die Taste Testen, um sicherzustellen, dass es funktioniert. Sie können diese Einstellungen jederzeit auf die Standardwerte zurücksetzen. Es gibt keine Möglichkeit, E-Mails für die Authentifizierung zu deaktivieren, wobei Sie nicht verpflichtet sind, eine gültige E-Mail-Adresse für Benutzer zu verwenden. Bestätigungslinks werden immer in Protokollen gespeichert und in der Benutzeroberfläche angezeigt. Registrierungs-/Bestätigungs-E-Mails werden nicht versendet, wenn Sie nicht über eine öffentlich erreichbare URL auf Kavita zugreifen oder wenn die Funktion Hostname nicht konfiguriert ist.", "send-to-warning": "", "email-url-label": "", "email-url-tooltip": "", @@ -939,7 +940,7 @@ "delete-library": "", "delete-library-by-name": "", "edit-library": "", - "edit-library-by-name": "" + "edit-library-by-name": "{{Name}} löschen" }, "manage-media-settings": { "encode-as-description-part-1": "", @@ -995,18 +996,18 @@ "allow-stats-label": "", "allow-stats-tooltip-part-1": "", "allow-stats-tooltip-part-2": "", - "send-data": "", + "send-data": "Daten senden", "opds-label": "", "opds-tooltip": "", "enable-opds": "", "folder-watching-label": "", "folder-watching-tooltip": "", "enable-folder-watching": "", - "reset-to-default": "", - "reset": "", - "save": "", - "cache-size-validation": "", - "field-required": "", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "Sie benötigen mindestens 50 MB.", + "field-required": "{{validation.field-required}}", "max-logs-validation": "", "min-logs-validation": "", "min-days-validation": "", @@ -1018,14 +1019,14 @@ }, "manage-system": { "title": "", - "version-title": "", + "version-title": "Version", "installId-title": "", "more-info-title": "", - "home-page-title": "", - "wiki-title": "", - "discord-title": "", - "donations-title": "", - "source-title": "", + "home-page-title": "Startseite:", + "wiki-title": "Wiki:", + "discord-title": "Discord:", + "donations-title": "Spenden:", + "source-title": "Quelle:", "feature-request-title": "" }, "manage-tasks-settings": { @@ -1068,33 +1069,33 @@ "check-for-updates-task-desc": "" }, "manage-users": { - "title": "", - "invite": "", - "you-alt": "", - "pending-title": "", - "delete-user-tooltip": "", - "delete-user-alt": "", - "edit-user-tooltip": "", - "edit-user-alt": "", - "resend-invite-tooltip": "", - "resend-invite-alt": "", - "setup-user-tooltip": "", - "setup-user-alt": "", - "change-password-tooltip": "", - "change-password-alt": "", - "resend": "", - "setup": "", - "last-active-title": "", - "roles-title": "", - "none": "", - "never": "", - "online-now-tooltip": "", - "sharing-title": "", - "no-data": "", - "loading": "" + "title": "Aktive Benutzer", + "invite": "Einladen", + "you-alt": "(Du)", + "pending-title": "Ausstehend", + "delete-user-tooltip": "Benutzer löschen", + "delete-user-alt": "Benutzer {{user}} löschen", + "edit-user-tooltip": "bearbeiten", + "edit-user-alt": "Benutzer {{user}} bearbeiten", + "resend-invite-tooltip": "Einladung erneut senden", + "resend-invite-alt": "Einladung erneut senden an {{user}}", + "setup-user-tooltip": "Benutzer einrichten", + "setup-user-alt": "Benutzer {{user}} einrichten", + "change-password-tooltip": "Passwort ändern", + "change-password-alt": "Passwort ändern {{user}}", + "resend": "Erneut senden", + "setup": "Einrichten", + "last-active-title": "Zuletzt aktiv:", + "roles-title": "Funktionen:", + "none": "Keine", + "never": "Nie", + "online-now-tooltip": "Jetzt online", + "sharing-title": "Teilen:", + "no-data": "Es gibt keine anderen Benutzer.", + "loading": "{{common.loading}}" }, "edit-collection-tags": { - "title": "", + "title": "Sammlung {{collectionName}} bearbeiten", "required-field": "{{validation.required-field}}", "save": "{{common.save}}", "close": "{{common.close}}", @@ -1116,7 +1117,7 @@ "recommended-tab": "Empfohlen" }, "library-recommended": { - "no-data": "", + "no-data": "Hier gibt es nichts zu sehen. Fügen Sie Ihrer Bibliothek einige Metadaten hinzu, lesen oder bewerten Sie etwas. In dieser Bibliothek können auch die Empfehlungen ausgeschaltet sein.", "more-in-genre": "", "rediscover": "", "highly-rated": "", @@ -1219,16 +1220,16 @@ }, "nav-header": { "skip-alt": "", - "search-series-alt": "", - "search-alt": "", + "search-series-alt": "Serie Suchen", + "search-alt": "Suchen…", "promoted": "", - "no-data": "", + "no-data": "Keine Ergebnisse gefunden", "scroll-to-top-alt": "", - "server-settings": "", - "settings": "", - "help": "", - "announcements": "", - "logout": "" + "server-settings": "Server Einstellungen", + "settings": "Einstellungen", + "help": "Hilfe", + "announcements": "Ankündigungen", + "logout": "Abmelden" }, "add-to-list-modal": { "title": "", @@ -1283,12 +1284,6 @@ "dark-theme-alt": "", "close-reader-alt": "" }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" - }, "manga-reader": { "back": "", "save-globally": "", @@ -1326,7 +1321,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", @@ -1682,6 +1676,6 @@ "issue-hash-num": "", "issue-num": "", "chapter-num": "", - "volume-num": "" + "volume-num": "Band" } } diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index e6a19197f5..a709b7da78 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -164,7 +164,7 @@ "user-holds": { "title": "Scrobble Holds", - "description": "This is a user-managed list of Series that will not be scrobbled to upstream providers. You can remove a series at any time and the next Scrobble-able event (reading progress, rating, want to read status) will trigger events." + "description": "This is a user-managed list of Series that will not be scrobbled to upstream providers. You can remove a series at any time and the next scrobble-able event (reading progress, rating, want to read status) will trigger events." }, "theme-manager": { @@ -267,6 +267,7 @@ "api-key": { "copy": "Copy", + "show": "Show", "regen-warning": "Regenerating your API key will invalidate any existing clients.", "no-key": "ERROR - KEY NOT SET", "confirm-reset": "This will invalidate any OPDS configurations you have setup. Are you sure you want to continue?", @@ -522,6 +523,7 @@ }, "all-series": { + "title": "All Series", "series-count": "{{common.series-count}}" }, @@ -704,7 +706,7 @@ "collections-title": "{{side-nav.collections}}", "reading-lists-title": "{{side-nav.reading-lists}}", - "writers-title": "Writers/Authors", + "writers-title": "Writers", "cover-artists-title": "Cover Artists", "characters-title": "Characters", "colorists-title": "Colorists", @@ -1067,7 +1069,16 @@ "save": "{{common.save}}", "media-issue-title": "Media Issues", - "scrobble-issue-title": "Scrobble Issues" + "scrobble-issue-title": "Scrobble Issues", + "cover-image-size-label": "Cover Image Size", + "cover-image-size-tooltip": "How large should cover images generate as. Note: Anything larger than the default will incur longer page load times." + }, + + "cover-image-size": { + "default": "Default (320x455)", + "medium": "Medium (640x909)", + "large": "Large (900x1277)", + "xlarge": "Extra Large (1265x1795)" }, "manage-scrobble-errors": { @@ -1322,7 +1333,8 @@ "read": "{{common.read}}", "read-options-alt": "Read options", "incognito-alt": "(Incognito)", - "no-data": "Nothing added" + "no-data": "Nothing added", + "characters-title": "{{series-metadata-detail.characters-title}}" }, "events-widget": { @@ -1435,7 +1447,7 @@ "close-reader-alt": "Close Reader" }, - "infinite-reader": { + "infinite-scroller": { "continuous-reading-prev-chapter-alt": "Scroll up to move to previous chapter", "continuous-reading-prev-chapter": "Previous Chapter", "continuous-reading-next-chapter-alt": "Scroll up to move to next chapter", @@ -1481,8 +1493,14 @@ "metadata-filter": { "filter-title": "Filter", + "sort-by-label": "Sort By", + "ascending-alt": "Ascending", + "descending-alt": "Descending", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "limit-label": "Limit To", + "format-label": "Format", - "format-tooltip": "This is library agnostic", "libraries-label": "Libraries", "collections-label": "Collections", "genres-label": "Genres", @@ -1509,13 +1527,7 @@ "series-name-tooltip": "Series name will filter against Name, Sort Name, or Localized Name", "release-label": "Release", "min": "Min", - "max": "Max", - "sort-by-label": "Sort By", - "ascending-alt": "Ascending", - "descending-alt": "Descending", - "reset": "{{common.reset}}", - "apply": "{{common.apply}}" - + "max": "Max" }, "sort-field-pipe": { @@ -1688,6 +1700,61 @@ "invalid-password-reset-url": "Invalid reset password url" }, + "metadata-builder": { + "or": "Match any of the following", + "and": "Match all of the following", + "add-rule": "Add Rule", + "remove-rule": "Remove Row" + }, + + "filter-field-pipe": { + "age-rating": "Age Rating", + "characters": "Characters", + "collection-tags": "Collection Tags", + "colorist": "Colorist", + "cover-artist": "Cover Artist", + "editor": "Editor", + "formats": "Formats", + "genres": "Genres", + "inker": "Inker", + "languages": "Languages", + "libraries": "Libraries", + "letterer": "Letterer", + "publication-status": "Publication Status", + "penciller": "Penciller", + "publisher": "Publisher", + "read-progress": "Read Progress", + "read-time": "Read Time", + "release-year": "Release Year", + "series-name": "Series Name", + "summary": "Summary", + "tags": "Tags", + "translators": "Translators", + "user-rating": "User Rating", + "writers": "Writers", + "path": "Path", + "file-path": "File Path" + }, + + "filter-comparison-pipe": { + "begins-with": "Begins with", + "contains": "Contains", + "equal": "Equal", + "greater-than": "Greater than", + "greater-than-or-equal": "Greater than or equal", + "less-than": "Less than", + "less-than-or-equal": "Less than or equal", + "matches": "Matches", + "does-not-contain": "Does not contain", + "not-equal": "Not equal", + "ends-with": "Ends with", + "is-before": "Is before", + "is-after": "Is after", + "is-in-last": "Is in last", + "is-not-in-last": "Is not in last", + "must-contains": "Must Contains" + }, + "toasts": { "regen-cover": "A job has been enqueued to regenerate the cover image", "no-pages": "There are no pages. Kavita was not able to read this archive.", @@ -1763,7 +1830,8 @@ "alert-bad-theme": "There is invalid or unsafe css in the theme. Please reach out to your admin to have this corrected. Defaulting to dark theme.", "confirm-library-delete": "Are you sure you want to delete the {{name}} library? You cannot undo this action.", "confirm-library-type-change": "Changing library type will trigger a new scan with different parsing rules and may lead to series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?", - "confirm-download-size": "The {{entityType}} is {{size}}. Are you sure you want to continue?" + "confirm-download-size": "The {{entityType}} is {{size}}. Are you sure you want to continue?", + "list-doesnt-exist": "This list doesn't exist" }, "actionable": { @@ -1790,7 +1858,11 @@ "view-series": "View Series", "clear": "Clear", "import-cbl": "Import CBL", - "read": "Read" + "read": "Read", + "add-rule-group-and": "Add Rule Group (AND)", + "add-rule-group-or": "Add Rule Group (OR)", + "remove-rule-group": "Remove Rule Group" + }, "preferences": { diff --git a/UI/Web/src/assets/langs/es.json b/UI/Web/src/assets/langs/es.json index ada81746d7..92c7cebb0c 100644 --- a/UI/Web/src/assets/langs/es.json +++ b/UI/Web/src/assets/langs/es.json @@ -1,30 +1,30 @@ { "login": { - "title": "Acceso", + "title": "Inicio de Sesión", "username": "{{common.username}}", "password": "{{common.password}}", - "password-validation": "La contraseña tiene que tener entre {{min}} y{{max}} caracteres", - "forgot-password": "Has olvidado la contraseña?", - "submit": "Enviar" + "password-validation": "{{validation.password-validation}}", + "forgot-password": "¿Has olvidado la contraseña?", + "submit": "{{common.submit}}" }, "dashboard": { "no-libraries": "No existen librerías configuradas. Configura algunas en", - "server-settings-link": "Ajustes del servidor", + "server-settings-link": "Configuración del servidor", "not-granted": "No has sido autorizado para ver ninguna librería.", - "on-deck-title": "On Deck", + "on-deck-title": "En la portada", "recently-updated-title": "Series Actualizadas Recientemente", "recently-added-title": "Series Añadidas Recientemente" }, "edit-user": { - "edit": "Editar", - "username": "Nombre de Usuario", - "not-valid-email": "El correo electronico tiene que ser válido", + "edit": "{{common.edit}}", + "username": "{{common.username}}", + "not-valid-email": "{{validation.valid-email}}", "saving": "Guardando …", "update": "Actualizar", - "required": "Este campo es obligatorio", - "close": "Cerrar", + "required": "{{validation.required-field}}", + "close": "{{common.close}}", "email": "dirección de correo", - "cancel": "Cancelar" + "cancel": "{{common.cancel}}" }, "user-scrobble-history": { "data-header": "Datos", @@ -37,7 +37,7 @@ "processed": "Procesado", "volume-and-chapter-num": "Volumen {{v}} Capítulo {{n}}", "not-processed": "Sin procesar", - "description": "Aquí encontrarás cualquier evento de scrobble vinculado a tu cuenta. Para que existan eventos, debes tener configurado\n un proveedor de scrobble activo. Todos los eventos que hayan sido procesados se eliminarán después de un mes. Si hay eventos no procesados, es\n probable que estos no puedan formar coincidencias en un nivel superior. Por favor, ponte en contacto con tu administrador para corregirlos.", + "description": "Aquí encontrarás cualquier evento de scrobbling vinculado a tu cuenta. Para que haya eventos, debes tener configurado un proveedor de scrobbling activo. Todos los eventos que hayan sido procesados se eliminarán después de un mes. Si hay eventos sin procesar, es probable que estos no puedan encontrar coincidencias en el proveedor. Por favor, ponte en contacto con tu administrador para corregirlos.", "title": "Historial de Scrobble", "series-header": "Series", "created-header": "Creado", @@ -47,7 +47,19 @@ "year-label": "Año", "month-label": "Mes", "promote-label": "Promocionar", - "promote-tooltip": "La promoción significa que la etiqueta puede ser vista en todo el servidor, no solo por los usuarios administradores. Todas las series que tengan esta etiqueta aún tendrán restricciones de acceso para los usuarios." + "promote-tooltip": "La promoción significa que la etiqueta puede ser vista en todo el servidor, no solo por los usuarios administradores. Todas las series que tengan esta etiqueta aún tendrán restricciones de acceso para los usuarios.", + "general-tab": "General", + "cover-image-tab": "Portada", + "close": "{{common.close}}", + "save": "{common.save}}", + "year-validation": "Debe ser mayor que 1000, 0 o blanco", + "month-validation": "Debe estar entre 1 y 12 o en blanco", + "required-field": "{{validation.required-field}}", + "summary-label": "Resumen", + "name-unique-validation": "El nombre debe ser único", + "starting-title": "Principio", + "title": "Editar Lista de Lectura: {{name}}", + "ending-title": "Final" }, "metadata-filter": { "penciller-label": "Dibujante", @@ -71,7 +83,20 @@ "unread": "Sin leer", "read": "Leído", "rating-label": "Calificación", - "age-rating-label": "Clasificación de edad" + "age-rating-label": "Clasificación de edad", + "publication-status-label": "Estado de publicación", + "max": "Máximo", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "series-name-label": "Nombre de la Serie", + "series-name-tooltip": "El nombre de la serie se filtrará por Nombre, Nombre ordenado o Nombre localizado", + "release-label": "Lanzamiento", + "min": "Mínimo", + "sort-by-label": "Ordenar Por", + "ascending-alt": "Ascendente", + "descending-alt": "Descendente", + "cover-artist-label": "Artista de la Portada", + "limit-label": "Limitar a" }, "manga-reader": { "incognito-title": "Modo incógnito:", @@ -93,23 +118,47 @@ "original": "Original", "brightness-label": "Brillo", "height": "Alto", - "width": "Ancho" + "width": "Ancho", + "no-next-chapter": "No hay siguiente Capítulo", + "layout-mode-switched": "El modo de diseño se ha cambiado a Individual ya que no hay espacio suficiente para renderizar el diseño doble", + "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}", + "first-time-reading-manga": "Pulsa en la imagen en cualquier momento para abrir el menú. Puedes configurar diferentes ajustes o ir a la página pulsando la barra de progreso. Pulsar en los laterales de la imagen te lleva a la siguiente/anterior página.", + "no-prev-chapter": "No hay Capítulo Anterior", + "fullscreen": "Pantalla completa", + "incognito-alt": "El modo incógnito está encendido. Desliza para apagar.", + "shortcuts-menu-alt": "Modo de atajos de teclado", + "collapse": "Ocultar", + "swipe-enabled-label": "Deslizamiento activado", + "prev-chapter-tooltip": "Capítulo/Volumen anterior", + "prev-page-tooltip": "Página anterior" }, "import-cbl-modal": { "import": "Importar", - "next": "Siguiente" + "next": "Siguiente", + "title": "Importación CBL", + "validate-description": "Todos los archivos se han validado para comprobar si hay operaciones pendientes en la lista. Cualquier lista que dé error no se moverá al siguiente paso. Repara los archivos CBL y vuelve a intentarlo.", + "import-description": "Para comenzar, importa un archivo .cbl. Kavita debe realizar varias comprobaciones antes de importar. Algunos pasos se bloquearán debido a fallos en el archivo.", + "validate-warning": "Hay problemas con el CBL que impiden la importación. Corrige estos fallos y prueba de nuevo.", + "validate-no-issue": "Parece correcto", + "validate-no-issue-description": "No hay fallos en el CBL, pulsa siguiente.", + "close": "{{common.close}}", + "import-step": "Importar CBL", + "dry-run-description": "Esto es una prueba y muestra que sucederá si presionas Siguiente y realizas la importación. Los fallos no se importarán.", + "prev": "Anterior", + "restart": "Reiniciar", + "validate-cbl-step": "Validar CBL", + "dry-run-step": "Prueba", + "final-import-step": "Último paso" }, "pdf-reader": { "incognito-mode": "Modo incógnito", "light-theme-alt": "Tema claro", - "dark-theme-alt": "Tema oscuro" - }, - "infinite-reader": { - "continuous-reading-prev-chapter": "Capítulo anterior", - "continuous-reading-next-chapter": "Capítulo siguiente" + "dark-theme-alt": "Tema oscuro", + "loading-message": "Cargando……los PDF pueden ser más lentos de lo esperado", + "close-reader-alt": "Cerrar Lector" }, "scrobble-event-type-pipe": { - "chapter-read": "Progreso de lectura", + "chapter-read": "Progreso en la lectura", "score-updated": "Actualización de calificación", "want-to-read-add": "Quiero leer: Añadir", "want-to-read-remove": "Quiero leer: Eliminar", @@ -121,7 +170,7 @@ "help": "Ayuda", "submit": "Enviar", "select-all": "Seleccionar todo", - "deselect-all": "Deseleccionar todo", + "deselect-all": "Anular selección", "apply": "Aplicar", "edit": "Editar", "loading": "Cargando…", @@ -130,15 +179,19 @@ "cancel": "Cancelar", "add": "Añadir", "save": "Guardar", - "reset": "Reiniciar", + "reset": "Restablecer", "delete": "Eliminar", - "username": "Usuario", + "username": "Nombre de usuario", "password": "Contraseña", "series-count": "{{num}} Series", "promoted": "Promocionado", - "reset-to-default": "Reiniciar a predeterminado", + "reset-to-default": "Restablecer valores predeterminados", "book-num": "Libro", - "chapter-num": "Capitulo" + "chapter-num": "Capítulo", + "email": "Correo electrónico", + "item-count": "{{num}} Elementos", + "issue-hash-num": "Número #", + "issue-num": "Número" }, "user-preferences": { "share-series-reviews-label": "Compartir reseñas de series", @@ -147,8 +200,8 @@ "reading-direction-book-tooltip": "Dirección para hacer clic para pasar a la siguiente página. De derecha a izquierda significa que haces clic en el lado izquierdo de la pantalla para pasar a la siguiente página.", "auto-close-menu-label": "Cerrar menú automáticamente", "immersive-mode-tooltip": "Esto ocultará el menú detrás de un clic en el documento del lector y activará tocar para paginar", - "layout-mode-book-tooltip": "Cómo se debe organizar el contenido. Desplazable es tal como el libro se empaqueta. 1 o 2 columnas se ajusta a la altura del dispositivo y ajusta 1 o 2 columnas de texto por página.", - "line-height-book-tooltip": "Cuánto espacio habrá entre las líneas del libro.", + "layout-mode-book-tooltip": "Cómo se debe organizar el contenido. Desplazable es tal como el libro se empaqueta. 1 o 2 columnas se ajusta a la altura del dispositivo y ajusta 1 o 2 columnas de texto por página", + "line-height-book-tooltip": "Cuánto espacio habrá entre las líneas del libro", "title": "Panel de usuario", "pref-description": "Estos son ajustes globales vinculados a tu cuenta.", "account-tab": "Cuenta", @@ -191,11 +244,11 @@ "immersive-mode-label": "Modo inmersivo", "reading-direction-book-label": "Dirección de lectura", "font-family-label": "Familia de fuentes", - "font-family-tooltip": "Familia de fuentes para cargar. Por defecto cargará la fuente predeterminada del libro.", + "font-family-tooltip": "Familia de fuentes para cargar. Por defecto cargará la fuente predeterminada del libro", "writing-style-label": "Estilo de escritura", "layout-mode-book-label": "Modo de diseño", "color-theme-book-label": "Tema de color", - "color-theme-book-tooltip": "Qué tema de color aplicar al contenido del lector de libros y al menú.", + "color-theme-book-tooltip": "Qué tema de color aplicar al contenido del lector de libros y al menú", "font-size-book-label": "Tamaño de fuente", "margin-book-label": "Margen", "margin-book-tooltip": "Cuánto espacio habrá en cada lado de la pantalla. Esto se anulará a 0 en los dispositivos móviles sin importar esta configuración.", @@ -209,7 +262,7 @@ "scrobbling-tab": "Seguimiento" }, "user-holds": { - "description": "Esta es una lista gestionada por el usuario de series que no serán scrobbleadas a los proveedores de flujo ascendente. Puedes eliminar una serie en\n cualquier momento y el siguiente evento Scrobble-able (progreso de lectura, calificación, estado de lectura deseado) activará eventos.", + "description": "Se trata de una lista gestionada por el usuario de series que no serán scrobbleadas a proveedores de upstream. Puedes eliminar una serie en cualquier momento y el siguiente evento scrobble-able (progreso de lectura, calificación, desea leer el estado) activará los eventos.", "title": "Retenciones de scrobble" }, "theme-manager": { @@ -260,7 +313,8 @@ "devices-title": "Dispositivos", "platform-label": "Plataforma: ", "add": "{{common.add}}", - "delete": "{{common.delete}}" + "delete": "{{common.delete}}", + "email-label": "Correo electrónico: " }, "edit-device": { "device-name-label": "Nombre del dispositivo", @@ -268,11 +322,12 @@ "required-field": "{{validation.required-field}}", "valid-email": "{{validation.valid-email}}", "device-platform-label": "Plataforma del dispositivo", - "save": "{{common.save}}" + "save": "{{common.save}}", + "email-tooltip": "Este correo electrónico se utilizará para aceptar el archivo a través de Enviar a" }, "restriction-selector": { "include-unknowns-tooltip": "Al estar activada, se mostraran objetos con clasificación de edad desconocida en las búsquedas. Esto puede provocar que se filtren series de mayor categoría a usuarios con restricción de edad.", - "title": "Clasificación por edad", + "title": "Restricción por edad", "description": "Cuando está seleccionado, las series y las listas de lectura que tengan una clasificación mayor serán ocultadas en los resultados.", "not-applicable-for-admins": "No se aplica a los administradores.", "age-rating-label": "Clasificación por edad", @@ -310,9 +365,9 @@ }, "change-age-restriction": { "save": "{{common.save}}", - "age-restriction-label": "Clasificación por edad", + "age-restriction-label": "Restricción de edad", "reset": "{{common.reset}}", - "unknowns": "Desconocido", + "unknowns": "Desconocidos", "edit": "{{common.edit}}", "cancel": "{{common.cancel}}" }, @@ -321,7 +376,8 @@ "key-reset": "Resetear Clave de API", "no-key": "ERROR - KEY NOT SET", "confirm-reset": "Esto invalidará cualquier configuración de OPDS. Estás seguro que quieres continuar?", - "regen-warning": "Regenerar tu Clave de API invalidará los clientes conectados." + "regen-warning": "Regenerar tu Clave de API invalidará los clientes conectados.", + "show": "Mostrar" }, "scrobbling-providers": { "generate": "Generar", @@ -350,16 +406,17 @@ "avg-reading-per-week-label": "Media de leídos por semana" }, "top-readers": { - "comics-label": "Comics: {{value}} hrs", + "comics-label": "Cómics: {{value}} hrs", "books-label": "Libros: {{value}} hrs", - "manga-label": "Manga: {{value}} hrs", + "manga-label": "Manga: {{value}} hrs.", "last-year": "{{time-periods.last-year}}", "all-time": "{{time-periods.all-time}}", "this-week": "{{time-periods.this-week}}", "last-7-days": "{{time-periods.last-7-days}}", "last-30-days": "{{time-periods.last-30-days}}", "last-90-days": "{{time-periods.last-90-days}}", - "title": "Top lectores" + "title": "Top lectores", + "time-selection-label": "Período" }, "directory-picker": { "path-placeholder": "Comienza a escribir o selecciona una ruta", @@ -370,7 +427,8 @@ "name-header": "Nombre", "cancel": "{{common.cancel}}", "share": "Compartir", - "help": "{{common.help}}" + "help": "{{common.help}}", + "instructions": "Selecciona una carpeta para ver la ruta de navegación. ¿No ves tu directorio? Intenta verificar primero las / ." }, "theme": { "theme-dark": "Oscuro", @@ -440,18 +498,35 @@ "chapter-missing": "{{series}}: Capítulo {{chapter}} no presente en Kavita. Este item será saltado.", "empty-file": "El archivo CBL está vacío. Ninguna acción realizada.", "name-conflict": "Una lista de lectura que coincide con el archivo CBL ({{readingListName}}) ya existe en tu cuenta.", - "series-collision": "La serie, {{seriesLink}}, tiene conflicto con otra serie en otra librería." + "series-collision": "La serie, {{seriesLink}}, tiene conflicto con otra serie en otra librería.", + "series-missing": "La serie, {{series}}, falta en Kavita o tu cuenta no tiene permiso. Todos los artículos con esta serie se omitirán de la importación.", + "volume-missing": "{{series}}: El volumen {{volume}} no existe en Kavita o tu cuenta no está autorizada. Todos los elementos con este número de volumen serán ignorados.", + "all-chapter-missing": "Todos los capítulos no pueden coincidir con los capítulos de Kavita.", + "invalid-file": "El archivo está dañado o no coincide con las etiquetas/especificaciones esperadas.", + "success": "{{series}} volumen {{volumen}} capítulo {{capítulo}} asignado correctamente." }, "library-type-pipe": { "manga": "Manga", "book": "Libro", - "comic": "Comic" + "comic": "Cómic" }, "age-rating-pipe": { "unknown": "Desconocido", "adults-only": "Solo adultos +18", "rating-pending": "Calificacion por edad pendiente", - "not-applicable": "No aplicable" + "not-applicable": "No aplicable", + "early-childhood": "Infancia Temprana", + "everyone": "Todos", + "everyone-10-plus": "Mayores de 10 años", + "kids-to-adults": "Desde niños hasta adultos", + "ma15-plus": "Mayores de 15 años", + "mature-17-plus": "Mayores de 17 años", + "teen": "Adolescentes", + "x18-plus": "Mayores de 18 años", + "pg": "Con acompañamiento adulto", + "r18-plus": "R18+", + "g": "Todas las edades", + "mature": "Mayor de edad" }, "series-detail": { "close": "{{common.close}}", @@ -467,7 +542,7 @@ "continue": "Continuar", "read-options-alt": "Opciones de lectura", "remove-from-want-to-read": "Quitar de Quiero leer", - "incognito": "Incognito", + "incognito": "Incógnito", "add-to-want-to-read": "Añadir a Quiero leer", "edit-series-alt": "Editar informacion de la serie", "download-series--tooltip": "Descargar serie", @@ -478,7 +553,8 @@ "send-to": "Archivo enviado a {{deviceName}}", "no-chapters": "No existen capitulos en este volumen. No se puede leer.", "cover-change": "Puede tomar hasta un minuto para que tu navegador actualice la imagen. Espera hasta entonces. La imagen antigua se puede mostrar en algunas páginas.", - "no-pages": "{{toasts.no-pages}}" + "no-pages": "{{toasts.no-pages}}", + "storyline-tab": "Argumento" }, "book-reader": { "go-to-page-prompt": "Hay {{totalPages}} páginas. A qué pagina quieres ir?", @@ -495,7 +571,7 @@ "go-to-page": "Ir a página", "prev-page": "Página anterior", "next-page": "Siguiente página", - "toc-header": "ToC", + "toc-header": "Índice", "loading-book": "Cargando libro…", "go-back": "Ir atrás", "incognito-mode-alt": "Modo incognito activado. Pulsa para desactivar.", @@ -506,7 +582,8 @@ }, "personal-table-of-contents": { "page": "Page {{value}}", - "delete": "Eliminar {{bookmarkName}}" + "delete": "Eliminar {{bookmarkName}}", + "no-data": "Todavía no se ha guardado nada en marcadores" }, "time-ago-pipe": { "never": "Nunca", @@ -543,7 +620,8 @@ "download": "Descarga", "available": "Disponible", "description": "Si no ves una etiqueta {{installed}}", - "description-continued": ", estás en una versión de desarrollo. Solo versiones estable aparecerán como disponible." + "description-continued": ", estás en una versión de desarrollo. Solo versiones estable aparecerán como disponible.", + "published-label": "Publicado: " }, "invite-user": { "setup-user-title": "Usuario invitado", @@ -577,7 +655,8 @@ "activate-license-label": "Clave de licencia", "activate-email-label": "{{common.email}}", "activate-delete": "Eliminar", - "activate-save": "{{common.save}}" + "activate-save": "{{common.save}}", + "title": "Licencia Kavita+" }, "series-metadata-detail": { "translators-title": "Traductores", @@ -587,11 +666,17 @@ "tags-title": "Etiquetas", "collections-title": "{{side-nav.collections}}", "reading-lists-title": "{{side-nav.reading-lists}}", - "writers-title": "Escritor/Autor", + "writers-title": "Escritores", "characters-title": "Persoanjes", "see-less": "Ver menos", "see-more": "Ver más", - "promoted": "{{common.promoted}}" + "promoted": "{{common.promoted}}", + "letterers-title": "Rotuladores", + "pencillers-title": "Plumillas", + "colorists-title": "Coloristas", + "editors-title": "Editores", + "inkers-title": "Entintadores", + "publishers-title": "Editoriales" }, "update-notification-modal": { "close": "{{common.close}}", @@ -611,7 +696,10 @@ "all-series": "Todas las series", "clear": "Limpiar", "donate": "Donar", - "reading-lists": "Listas de lectura" + "reading-lists": "Listas de lectura", + "bookmarks": "Marcadores", + "home": "Inicio", + "want-to-read": "Quiero Leer" }, "library-settings-modal": { "close": "{{common.close}}", @@ -632,11 +720,35 @@ "naming-conventions-part-1": "Kavita tiene ", "naming-conventions-part-2": "requerimientos de carpetas.", "naming-conventions-part-3": "Comprueba este enlace para asegurarte que estás siguiendo los requerimientos. De lo contrario algunos archivos no se mostrarán al escanear.", - "allow-scrobbling-label": "Permitir seguimiento", - "allow-scrobbling-tooltip": "En caso de que Kavita scrobble lea los eventos, quiera leer el estado, las calificaciones y las reseñas de los proveedores configurados. Esto solo ocurrirá si el servidor tiene una suscripción Kavita+ activa." + "allow-scrobbling-label": "Permitir scrobbling", + "allow-scrobbling-tooltip": "En caso de que Kavita scrobble lea los eventos, quiera leer el estado, las calificaciones y las reseñas de los proveedores configurados. Esto solo ocurrirá si el servidor tiene una suscripción Kavita+ activa.", + "include-in-recommendation-label": "Incluir en Recomendados", + "include-in-dashboard-label": "Incluir en Panel de Control", + "include-in-recommendation-tooltip": "Incluir series de la biblioteca en la página de Recomendados.", + "include-in-search-label": "Incluir en Búsqueda", + "manage-collection-label": "Gestionar Colecciones", + "manage-collection-tooltip": "¿Debería Kavita crear colecciones a partir de las etiquetas SeriesGroup que se encuentran en los archivos ComicInfo.xml/opf?", + "type-tooltip": "El tipo de biblioteca determina como se analizan los nombres de los archivos y si la interfaz muestra Capítulos (Manga) o Números (Cómics). Los libros funcionan igual que los Mangas pero se muestran los nombres de manera diferente en la interfaz.", + "browse": "Examinar las Carpetas Multimedia", + "cover-description": "Los iconos de bibliotecas personalizadas son opcionales", + "cover-description-extra": "Las imágenes de biblioteca no deben ser grandes. Usa un archivo pequeño, de 32x32 píxeles. Kavita no valida por tamaño.", + "manage-reading-list-label": "Gestionar Listas de Lectura", + "folder-watching-label": "Monitorizar Carpetas", + "include-in-dashboard-tooltip": "¿Deben incluirse las series de la biblioteca en el panel de control? Esto afecta a todas las fuentes, como \"En el puente\", \"Actualizado recientemente\", \"Añadido recientemente\" o cualquier adición personalizada.", + "folder-watching-tooltip": "Anula la monitorización de carpetas del servidor para esta biblioteca. Si está desactivada, la monitorización de carpetas no se ejecutará en las carpetas que contenga esta biblioteca. Si las bibliotecas comparten carpetas, éstas pueden seguir siendo monitorizadas.", + "force-scan": "Escaneo forzado", + "include-in-search-tooltip": "¿Deben incluirse las series y cualquier información derivada (géneros, personas, archivos) de la biblioteca en los resultados de búsqueda?", + "manage-reading-list-tooltip": "¿Debe Kavita crear listas de lectura a partir de las etiquetas StoryArc/StoryArcNumber y AlternativeSeries/AlternativeCount que se encuentran en los archivos ComicInfo.xml/opf?", + "force-scan-tooltip": "Esto forzará un escaneo de la biblioteca, tratándolo como un escaneo nuevo", + "next": "Siguiente", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}" }, "all-series": { - "series-count": "{{common.series-count}}" + "series-count": "{{common.series-count}}", + "title": "Todas las series" }, "announcements": { "title": "Noticias" @@ -658,7 +770,10 @@ "sequel": "Secuela", "side-story": "Historia alternativa", "parent": "Padre", - "edition": "Edicion" + "edition": "Edicion", + "alternative-setting": "Ajuste Alternativo", + "spin-off": "Spin-Off", + "doujinshi": "Doujinshi" }, "publication-status-pipe": { "ongoing": "En curso", @@ -673,7 +788,12 @@ "character": "Personaje", "cover-artist": "Artista de portada", "editor": "Editor", - "other": "Otro" + "other": "Otro", + "publisher": "Editorial", + "colorist": "Colorista", + "inker": "Entintador", + "letterer": "Rotulista", + "penciller": "Dibujante" }, "manga-format-pipe": { "unknown": "Desconocido", @@ -734,5 +854,729 @@ "read-more": { "read-more": "Leer más", "read-less": "Leer menos" + }, + "role-selector": { + "title": "Roles" + }, + "validation": { + "valid-email": "Este debe ser un correo electrónico válido", + "required-field": "Este campo es obligatorio", + "password-validation": "La contraseña debe tener entre 6 y 32 caracteres" + }, + "entity-type": { + "bookmark": "marcador", + "logs": "registros", + "volume": "volumen", + "chapter": "capítulo" + }, + "actionable": { + "settings": "Ajustes", + "analyze-files": "Analizar Archivos", + "scan-library": "Escanear Biblioteca", + "read": "Leer", + "import-cbl": "Importar CBL", + "clear": "Limpiar", + "mark-as-unread": "Marcar como no leído", + "mark-as-read": "Marcar como leído", + "refresh-covers": "Actualizar Portadas", + "edit": "Editar", + "delete": "Borrar", + "download": "Descargar", + "others": "Otros" + }, + "preferences": { + "automatic": "Automático", + "vertical": "Vertical", + "horizontal": "Horizontal", + "1-column": "1 Columna", + "list": "Lista", + "cards": "Tarjetas", + "right-to-left": "Derecha a izquierda", + "left-to-right": "Izquierda a derecha", + "2-column": "2 Columnas", + "double": "Doble", + "original": "Original" + }, + "toasts": { + "confirm-regen-covers": "Actualizar portadas obligará a recalcular todas las imágenes de las portadas. Se trata de una operación pesada. ¿Está seguro de que no desea realizar un escaneo en su lugar?", + "load-next-chapter": "Siguiente {{entity}} cargada", + "load-prev-chapter": "Anterior {{entity}} cargada", + "no-prev-chapter": "No se pudo encontrar la anterior {{entity}}", + "account-registration-complete": "Registro completado", + "account-migration-complete": "Migración de cuenta completada", + "password-updated": "La contraseña se ha actualizado", + "anilist-token-updated": "Se ha actualizado el Token AniList", + "age-restriction-updated": "Se ha actualizado la restricción de edad", + "email-sent-to": "Se ha enviado un correo electrónico a tu antigua dirección de correo para confirmación.", + "device-updated": "Dispositivo actualizado", + "device-created": "Dispositivo creado", + "confirm-delete-multiple-series": "¿Estás seguro de que deseas eliminar la serie {{count}}? No se modificarán los archivos en el disco.", + "forced-scan-queued": "Se ha iniciado un escaneo forzoso de {{name}}", + "email-sent-to-no-existing": "Se ha enviado un correo electrónico a {{email}} para confirmación.", + "password-reset": "Restablecer contraseña", + "library-created": "Biblioteca creada con éxito. Se ha iniciado un escaneo.", + "alert-long-running": "Este proceso es de larga duración. Por favor, dale tiempo a completarse antes de volverlo a iniciar.", + "change-email-private": "No se puede acceder al servidor de forma externa. Pídele tu enlace de confirmación al administrador", + "alert-bad-theme": "Hay un css no válido o inseguro en el tema. Por favor, ponte en contacto con tu administrador para corregirlo. Estableciendo el tema oscuro por defecto.", + "confirm-delete-series": "¿Estás seguro de que deseas eliminar esta serie? No se modificarán los archivos en el disco.", + "list-doesnt-exist": "Esta lista no existe", + "item-removed": "Elemento eliminado", + "collection-updated": "Colección actualizada" + }, + "library-selector": { + "title": "Bibliotecas", + "select-all": "{{common.select-all}}", + "no-data": "Todavía no se han configurado bibliotecas.", + "deselect-all": "{{common.deselect-all}}" + }, + "grouped-typeahead": { + "people": "Gente", + "tags": "Etiquetas", + "chapters": "Capítulos", + "files": "Archivos", + "reading-lists": "Listas de lectura", + "genres": "Géneros", + "collections": "Colecciones", + "libraries": "Bibliotecas", + "close": "{{common.close}}", + "loading": "{{common.loading}}" + }, + "shortcuts-modal": { + "go-to": "Abrir el diálogo de Ir a la Página", + "prev-page": "Ir a la página anterior", + "next-page": "Ir a la página siguiente", + "toggle-menu": "Abrir/cerrar Menú", + "bookmark": "Añadir página actual a marcadores", + "double-click": "Doble clic", + "close-reader": "Cerrar lector", + "title": "Atajos de Teclado", + "close": "{{common.close}}" + }, + "sort-field-pipe": { + "sort-name": "Ordenar Nombre", + "release-year": "Año de lanzamiento", + "last-modified": "Modificado por última vez", + "last-chapter-added": "Item añadido", + "time-to-read": "Tiempo de lectura", + "created": "Creado" + }, + "edit-series-modal": { + "title": "{{seriesName}} Detalles", + "general-tab": "General", + "cover-image-tab": "Imagen de portada", + "related-tab": "Relacionado", + "editor-label": "Editor", + "colorist-label": "Colorista", + "publication-status-title": "Estado de publicación:", + "info-tab": "Información", + "collections-label": "Colecciones", + "genres-label": "Géneros", + "created-title": "Creado:", + "size-title": "Tamaño:", + "loading": "{{common.loading}", + "tags-label": "Etiquetas", + "cover-artist-label": "Artista de portada", + "writer-label": "Escritor", + "publisher-label": "Editorial", + "penciller-label": "Dibujante", + "letterer-label": "Rotulador", + "inker-label": "Entintador", + "character-label": "Personaje", + "age-rating-label": "Clasificación por edad", + "publication-status-label": "Estado de publicación", + "release-year-label": "Año de lanzamiento", + "web-link-description": "Aquí puedes añadir enlaces a servicios externos.", + "info-title": "Información", + "library-title": "Biblioteca:", + "format-title": "Formato:", + "last-read-title": "Última lectura:", + "last-added-title": "Último Ítem Añadido:", + "folder-path-title": "Ruta de la Carpeta:", + "total-items-title": "Ítems totales:", + "total-pages-title": "Páginas totales:", + "max-items-title": "Ítems máximos:", + "metadata-tab": "Metadatos", + "people-tab": "Gente", + "web-links-tab": "Enlaces web", + "translator-label": "Traductor", + "language-label": "Idioma", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "sort-name-label": "Ordenar Nombre", + "add-link-alt": "Añadir Enlace", + "remove-link-alt": "Quitar Enlace", + "cover-image-description": "Subir y elegir nueva portada. Pulsa Guardar para subir y sustituir la portada.", + "save": "{{common.save}}", + "field-locked-alt": "Campo bloqueado", + "last-scanned-title": "Último escaneo:", + "view-files": "Ver Archivos", + "added-title": "Añadido:", + "last-modified-title": "Última modificación:", + "pages-title": "Páginas:", + "chapter-title": "Capítulo:", + "volume-num": "{{common.volume-num}}", + "summary-label": "Resumen", + "web-link-label": "Enlace web", + "name-label": "Nombre" + }, + "nav-header": { + "search-alt": "Buscar…", + "skip-alt": "Saltar al contenido principal", + "search-series-alt": "Buscar series", + "no-data": "No se han encontrado resultados", + "scroll-to-top-alt": "Navegar hasta arriba", + "server-settings": "Ajustes del servidor", + "logout": "Cerrar sesión", + "settings": "Ajustes", + "help": "Ayuda", + "announcements": "Anuncios", + "promoted": "(ascendido)" + }, + "add-to-list-modal": { + "title": "Añadir a Lista de Lectura", + "filter-label": "Filtro", + "close": "{{common.close}}", + "no-data": "Todavía no se han creado listas", + "loading": "{{common.loading}}", + "reading-list-label": "Lista de Lectura", + "create": "{{common.create}}", + "promoted-alt": "Ascendido" + }, + "reading-lists": { + "create-one-part-1": "Prueba a crear", + "create-one-part-2": "uno", + "title": "Listas de lectura", + "item-count": "{{common.item-count}}", + "no-data": "No hay listas de lectura." + }, + "reading-list-item": { + "remove": "{{common.remove}}", + "read": "{{common.read}}" + }, + "reading-list-detail": { + "no-data": "No se ha añadido nada", + "item-count": "{{common.item-count}}", + "page-settings-title": "Ajustes de Página", + "remove-read": "Quitar Leídos", + "order-numbers-label": "Ordenar Números", + "continue": "Continuar", + "read": "{{common.read}}", + "read-options-alt": "Opciones de lectura", + "incognito-alt": "(Incógnito)", + "characters-title": "{{series-metadata-detail.characters-title}}" + }, + "events-widget": { + "title-alt": "Actividad", + "dismiss-all": "Descartar Todo", + "update-available": "Actualización disponible", + "downloading-item": "Descargando {{item}}", + "more-info": "Pincha para más información", + "close": "{{common.close}}", + "users-online-count": "{{num}} Usuarios en línea", + "active-events-title": "Eventos activos:", + "no-data": "No hay mucha actividad por aquí" + }, + "reader-settings": { + "reset-to-defaults": "Restablecer los valores predeterminados", + "right-to-left": "Derecha a izquierda", + "left-to-right": "Izquierda a derecha", + "theme-dark": "Oscuro", + "theme-white": "Blanco", + "theme-paper": "Papel", + "reader-settings-title": "Ajustes del lector", + "fullscreen-tooltip": "Poner lector en modo de pantalla completa", + "on": "Encendido", + "exit": "Salir", + "enter": "Entrar", + "layout-mode-option-2col": "2 Columnas", + "general-settings-title": "Ajustes generales", + "off": "Apagado", + "fullscreen-label": "Pantalla completa", + "layout-mode-option-1col": "1 Columna", + "color-theme-title": "Tema de la interfaz", + "theme-black": "Negro", + "vertical": "Vertical", + "writing-style-tooltip": "Cambia la dirección del texto. Horizontal es de izquierda a derecha, vertical es de arriba a abajo.", + "tap-to-paginate-tooltip": "Pulsa los bordes de la pantalla para paginar", + "horizontal": "Horizontal", + "line-spacing-label": "{{user-preferences.line-height-book-label}}", + "font-family-label": "{{user-preferences.font-family-label}}", + "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "margin-label": "{{user-preferences.margin-book-label}}", + "font-size-label": "{{user-preferences.font-size-book-label}}", + "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-tooltip": "Esto ocultará el menú tras un clic en el documento de lectura y activará el toque para enumerar las páginas", + "layout-mode-tooltip": "Desplazamiento: duplica el archivo epub (generalmente una página de desplazamiento largo por capítulo).\n
    1 columna: crea una sola página virtual a la vez.
    2 columnas: crea dos páginas virtuales a la vez dispuestas una al lado de la otra.", + "layout-mode-option-scroll": "Desplazamiento", + "writing-style-label": "{{user-preferences.writing-style-label}}", + "tap-to-paginate-label": "Pulsa para enumerar las páginas" + }, + "cover-image-chooser": { + "upload": "Subir", + "reset-cover-tooltip": "Restablecer Imagen de Portada", + "drag-n-drop": "Arrastrar y soltar", + "upload-continued": "una imagen", + "url-label": "Url", + "load": "Cargar", + "back": "Atrás", + "image-num": "Imagen {{num}}", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "applied": "{{theme-manager.applied}}" + }, + "edit-series-relation": { + "description-part-1": "¿No estás seguro de qué relación añadir? Mira nuestro", + "description-part-2": "wiki para obtener pistas.", + "target-series": "Series objetivo", + "relationship": "Relación", + "remove": "Eliminar", + "add-relationship": "Añadir Relación", + "parent": "{{relationship-pipe.parent}}" + }, + "entity-info-cards": { + "pages-count": "{{num}} Páginas", + "words-count": "{{num}} Palabras", + "reading-time-title": "Tiempo de lectura", + "length-title": "Longitud", + "less-than-hour": "<1 Hora", + "release-date-title": "Lanzamiento", + "release-date-tooltip": "Fecha de Lanzamiento", + "age-rating-title": "Clasificación por Edad", + "isbn-title": "ISBN", + "last-read-title": "Última lectura", + "size-title": "Tamaño", + "id-title": "Identificador", + "hour": "Hora", + "hours": "Horas", + "date-added-title": "Fecha añadida", + "links-title": "{{series-metadata-detail.links-title}}", + "read-time-title": "{{series-info-cards.read-time-title}}", + "range-hours": "{{value}} {{hourWord}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}" + }, + "series-info-cards": { + "length-title": "Longitud", + "read-time-title": "Tiempo de lectura", + "time-left-title": "Tiempo restante", + "last-read-title": "Última lectura", + "publication-status-title": "Publicación", + "disabled": "Deshabilitado", + "format-title": "Formato", + "release-year-tooltip": "Año de lanzamiento", + "language-title": "Idioma", + "publication-status-tooltip": "Estado de publicación", + "on": "Encendido", + "off": "Apagado", + "ongoing": "{{publication-status-pipe.ongoing}}", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}", + "release-date-title": "{{entity-info-cards.release-date-title}}", + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "scrobbling-title": "Scrobbling", + "scrobbling-tooltip": "Estado del scrobbling" + }, + "bulk-add-to-collection": { + "filter-label": "Filtro", + "no-data": "Todavía no se han creado colecciones", + "collection-label": "Colección", + "title": "Añadir a la colección", + "clear": "{{common.clear}}", + "loading": "{{common.loading}}", + "close": "{{common.close}}", + "promoted": "{{common.promoted}}", + "create": "{{common.create}}" + }, + "bookmarks": { + "no-data-2": "uno.", + "confirm-delete": "¿Estás seguro de que deseas limpiar todos los marcadores para varias series? Esto no se puede deshacer.", + "confirm-single-delete": "¿Estás seguro de que deseas limpiar todos los marcadores de {{seriesName}}? Esto no se puede deshacer.", + "delete-success": "Se han eliminado los marcadores", + "delete-single-success": "Los marcadores de la serie {{seriesName}} se han eliminado", + "no-data": "No hay marcadores. Prueba a crearlos", + "series-count": "{{common.series-count}}", + "title": "{{side-nav.bookmarks}}" + }, + "card-detail-drawer": { + "not-defined": "No definido", + "unread": "No leído", + "pages": "Páginas:", + "added": "Añadido:", + "size": "Tamaño:", + "general-tab": "General", + "metadata-tab": "Metadatos", + "cover-tab": "Portada", + "info-tab": "Información", + "no-summary": "No hay un resumen disponible.", + "files": "Archivos", + "read": "{{common.read}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "writers-title": "{{series-metadata-detail.writers-title}}", + "genres-title": "{{series-metadata-detail.genres-title}}" + }, + "chapter-metadata-detail": { + "no-data": "No hay metadatos disponibles", + "writers-title": "{{series-metadata-detail.writers-title}}", + "editors-title": "{{series-metadata-detail.editors-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "translators-title": "{{series-metadata-detail.translators-title}}", + "letterers-title": "{{series-metadata-detail.letterers-title}}", + "inkers-title": "{{series-metadata-detail.inkers-title}}", + "pencillers-title": "{{series-metadata-detail.pencillers-title}}", + "cover-artists-title": "{{series-metadata-detail.cover-artists-title}}", + "colorists-title": "{{series-metadata-detail.colorists-title}}" + }, + "table-of-contents": { + "no-data": "Este libro no tiene Tabla de Contenidos definida en los metadatos o archivo toc" + }, + "download-indicator": { + "progress": "{{percentage}}% descargado" + }, + "bulk-operations": { + "title": "Acciones en bloque", + "mark-as-unread": "Marcar como no leído", + "mark-as-read": "Marcar como leído", + "items-selected": "{{num}} elementos seleccionados", + "deselect-all": "{{common.deselect-all}}" + }, + "entity-title": { + "special": "Especial", + "chapter": "Capítulo", + "issue-num": "Problema #" + }, + "external-series-card": { + "open-external": "Abrir externamente" + }, + "manage-alerts": { + "description-part-1": "Esta tabla contiene los problemas encontrados durante el escaneo o la lectura de tus medios. Esta lista no está gestionada. Puedes borrarla en cualquier momento y utilizar Escaneado (forzado) de la biblioteca para realizar el análisis. Puedes encontrar una lista de algunos errores comunes y su significado en ", + "file-header": "Archivo", + "comment-header": "Comentario", + "description-part-2": "wiki.", + "details-header": "Detalles", + "filter-label": "Filtrar", + "clear-alerts": "Limpiar alertas", + "extension-header": "Extensión" + }, + "manage-email-settings": { + "send-to-warning": "Si quieres que Enviar a un dispositivo funcione debes alojar tu propio servicio de correo electrónico.", + "email-url-label": "URL del servicio de correo", + "description": "Kavita trae de serie un servicio de email para realizar tareas como invitar usuarios, solicitudes de restablecimiento de contraseña, etc. Los emails enviados por este medio son borrados inmediatamente. Puedes usar tu propio servicio de email configurando el servicio {{link}}. Añade la URL del servicio de email y comprueba que funciona mediante el botón Test. Puedes restaurar esta configuración a sus valores predeterminados en cualquier momento. No es posible deshabilitar los emails de autenticación, aunque no se requiere usar una dirección de email válida para los usuarios. Los enlaces de confirmación serán siempre guardados en los registros y mostrados en la interfaz gráfica. Los emails de registro/confirmación no serán enviados si no estás accediendo a Kavita por una URL públicamente accesible o a menos que la característica Host Name esté configurada.", + "title": "Servicios de correo electrónico (SMTP)", + "reset": "{{common.reset}}", + "test": "Test", + "email-url-tooltip": "Utiliza la dirección URL completa del servicio de correo electrónico. No incluyas la barra oblicua final.", + "host-name-label": "Nombre del host", + "host-name-tooltip": "Nombre de dominio (del proxy inverso). Si se indica, el generador de correos electrónicos usará siempre este.", + "host-name-validation": "El nombre del anfitrión debe empezar con http(s) y no acabar en /", + "save": "{{common.save}}", + "reset-to-default": "{{common.reset-to-default}}" + }, + "list-item": { + "read": "{{common.read}}" + }, + "cover-image-size": { + "xlarge": "Extragrande (1265x1795)", + "default": "Por defecto (320x455)", + "medium": "Mediano (640x909)", + "large": "Grande (900x1277)" + }, + "manage-scrobble-errors": { + "description": "Esta tabla contiene los problemas encontrados durante el scrobbling. Esta lista no está administrada. Puedes borrarla en cualquier momento y esperar a la siguiente carga de scrobble para verla. Si hay una serie desconocida, lo mejor es corregir el nombre de la serie o el nombre localizado de la serie o añadir un enlace web para los proveedores.", + "series-header": "Series", + "created-header": "Creado", + "comment-header": "Comentario", + "edit-header": "Editar", + "edit-item-alt": "Editar {{seriesName}}", + "clear-errors": "Borrar los fallos", + "filter-label": "Filtro" + }, + "card-detail-layout": { + "total-items": "Elementos totales {{count}}" + }, + "card-item": { + "cannot-read": "No se puede leer" + }, + "default-date-pipe": { + "never": "Nunca" + }, + "manage-settings": { + "notice": "Noticia:", + "restart-required": "Cambiar el puerto, la URL base, el tamaño de caché o las direcciones IP requiere un reinicio manual de Kavita para que surta efecto.", + "base-url-label": "URL base", + "log-tooltip": "Número de registros a mantener. Por defecto 30, como mínimo 1 y cómo máximo 30.", + "cache-size-validation": "Debes tener al menos 50 MB.", + "ip-address-label": "Direcciones IP", + "port-label": "Puerto", + "backup-label": "Días de copias de seguridad", + "backup-tooltip": "Número de copias de seguridad a mantener. Por defecto 30, como mínimo 1, como máximo 30.", + "log-label": "Días de registros", + "port-tooltip": "Puerto en el que el servidor escucha. Esto es fijo si se ejecuta en Docker. Requiere reinicio para ser aplicado.", + "ip-address-tooltip": "Lista de direcciones IP separadas por comas en las que el servidor escucha. Esto es fijo si se ejecuta en Docker. Requiere reinicio para ser aplicado.", + "cache-size-label": "Tamaño de caché", + "cache-size-tooltip": "Cantidad de memoria permitida para almacenar APIs pesadas en caché. Por defecto, 75MB.", + "opds-label": "OPDS", + "enable-opds": "Habilitar OPDS", + "send-data": "Enviar datos", + "base-url-tooltip": "Usa esto si quieres alojar Kavita en una url base ej. tudominio.com/kavita. No soportado si se usa Docker como usuario no-root.", + "logging-level-label": "Nivel de registro", + "logging-level-tooltip": "Use la depuración para ayudar a identificar problemas. La depuración puede consumir mucho espacio en disco.", + "folder-watching-label": "Monitorizar carpetas", + "folder-watching-tooltip": "Permite a Kavita monitorizar las carpetas de biblioteca para detectar cambios e iniciar escaneos de dichos cambios. Esto permite que el contenido se actualice sin tener que iniciar escaneos manualmente o esperar a los escaneos nocturnos.", + "enable-folder-watching": "Habilitar monitoreo de carpetas", + "max-logs-validation": "No puedes tener más de {{num}} registros", + "min-logs-validation": "Tienes que tener al menos un registro", + "min-days-validation": "Debe ser al menos un día", + "min-cache-validation": "Debe estar a 50 MB.", + "max-backup-validation": "No puedes tener más de {{num}} copias de seguridad", + "min-backup-validation": "Debes tener al menos 1 copia de seguridad", + "ip-address-validation": "Las direcciones IP pueden contener solamente direcciones IPv4 o IPV6 válidas", + "base-url-validation": "La URL base debe comenzar y terminar con /", + "allow-stats-label": "Permitir la recopilación de datos de uso anónimos", + "allow-stats-tooltip-part-1": "Enviar datos anónimos de uso a los servidores de Kavita. Esto incluye información sobre el uso de ciertas características, número de archivos, versión de S.O., versión de Kavita instalada, CPU y memoria. Usaremos esta información para priorizar características, reparaciones y optimización del rendimiento. Es necesario reiniciar para que funcione. Mira el ", + "allow-stats-tooltip-part-2": "para ver qué datos se recopilan.", + "opds-tooltip": "El soporte de OPDS permitirá a los usuarios usar OPDS para leer y descargar contenido desde el servidor.", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "field-required": "{{validation.field-required}}" + }, + "manage-media-settings": { + "encode-as-description-part-1": "WebP/AVIF puede reducir drásticamente el espacio requerido para archivos. WebP/AVIF no está soportado por todos los navegadores o versiones. Para comprobar si estos ajustes son apropiados para tu configuración, visita ", + "encode-as-description-part-2": "¿Puedo usar WebP?", + "encode-as-description-part-3": "¿Puedo usar AVIF?", + "encode-as-warning": "No puedes convertir de nuevo a PNG una vez ya has obtenido WebP/AVIF. Necesitarías recargar las portadas en tus bibliotecas para regenerar todas las portadas. Los marcadores y favicons no se pueden convertir.", + "cover-image-size-label": "Tamaño de la imagen de portada", + "media-warning": "Debes iniciar la tarea de conversión multimedia en la pestaña Tareas.,", + "encode-as-label": "Guardar Media como", + "encode-as-tooltip": "Toda la multimedia que Kavita gestiona (portadas, marcadores, favicons) serán codificados con este tipo.", + "bookmark-dir-label": "Directorio de marcadores", + "bookmark-dir-tooltip": "Lugar en el que se almacenarán los marcadores. Los marcadores son archivos de origen y pueden ser grandes. Elige un lugar con el almacenamiento adecuado. El directorio es gestionado; otros archivos que existan en el directorio serán eliminados. Si estás utilizando Docker, monta un volumen adicional y úsalo.", + "change": "Cambiar", + "media-issue-title": "Problemas multimedia", + "scrobble-issue-title": "Problemas con el scrobbling", + "cover-image-size-tooltip": "Con qué tamaño deberían generarse las imágenes de portada. Nota: Cualquier tamaño mayor que el establecido por defecto provocará tiempo de carga mayores.", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "reset-to-default": "{{common.reset-to-default}}" + }, + "all-collections": { + "title": "Colecciones", + "create-one-part-2": "una", + "item-count": "{{common.item-count}}", + "create-one-part-1": "Prueba a crear", + "no-data": "No hay colecciones." + }, + "reading-activity": { + "x-axis-label": "Tiempo", + "legend-label": "Formatos" + }, + "admin-dashboard": { + "kavita+-desc-part-3": "hoy!", + "general-tab": "General", + "users-tab": "Usuarios", + "libraries-tab": "Bibliotecas", + "logs-tab": "Registros", + "email-tab": "Correo electrónico", + "tasks-tab": "Tareas", + "statistics-tab": "Estadísticas", + "system-tab": "Sistema", + "kavita+-tab": "Kavita+", + "title": "Panel de administrador", + "media-tab": "Multimedia", + "kavita+-desc-part-1": "Kavita+ es un servicio de suscripción premium que desbloquea características para todos los usuarios en esta instancia de Kavita. Compra una suscripción para desblquear. ", + "kavita+-desc-part-2": "Ventajas del premium" + }, + "file-breakdown-stats": { + "visualisation-label": "Visualización", + "format-title": "Formato", + "format-header": "Formato", + "total-size-header": "Tamaño total" + }, + "filter-field-pipe": { + "colorist": "Colorista", + "formats": "Formatos", + "inker": "Entintador", + "languages": "Idiomas", + "libraries": "Bibliotecas", + "letterer": "Rotulista", + "penciller": "Dibujante", + "summary": "Resumen", + "tags": "Etiquetas", + "translators": "Traductores", + "characters": "Personajes", + "publisher": "Editorial", + "genres": "Géneros", + "release-year": "Año de lanzamiento", + "writers": "Escritores" + }, + "filter-comparison-pipe": { + "contains": "Contiene", + "equal": "Igual a", + "greater-than": "Mayor que" + }, + "manage-system": { + "more-info-title": "Más información", + "wiki-title": "Wiki:", + "discord-title": "Discord:", + "donations-title": "Donaciones:", + "source-title": "Fuente:", + "version-title": "Versión", + "feature-request-title": "Solicitudes de funciones", + "installId-title": "Instalar ID", + "home-page-title": "Página de inicio:", + "title": "Sobre el sistema" + }, + "manage-tasks-settings": { + "action-header": "Acción", + "check-for-updates-task": "Buscar actualizaciones", + "cron-header": "Cron", + "description-header": "Descripción", + "download-logs-task-desc": "Compila todos los archivos de registro en un zip y lo descarga.", + "title": "Tareas recurrentes", + "library-scan-label": "Escaneo de biblioteca", + "download-logs-task": "Descargar registros", + "library-database-backup-label": "Copia de seguridad de la base de datos de la biblioteca", + "bust-cache-task-desc": "Eliminar la caché de Kavita+ - solamente se debería usar cuando se depuren malas coincidencias.", + "convert-media-task-desc": "Inicia una tarea de ejecución lenta que convertirá todos los archivos gestionados por Kavita a la codificación objetivo. Esto es lento (especialmente en dispositivos ARM).", + "adhoc-tasks-title": "Tareas ad-hoc", + "library-database-backup-tooltip": "Cada cuánto tiempo Kavita hará una copia de seguridad de la base de datos.", + "job-title-header": "Puesto de trabajo", + "bust-cache-task": "Eliminar caché", + "bust-cache-task-success": "Caché de Kavita+ eliminada", + "clear-reading-cache-task": "Limpiar caché de lectura", + "backup-database-task-success": "Se ha puesto en cola una tarea para realizar una copia de seguridad de la base de datos", + "analyze-files-task": "Analizar archivos", + "last-executed-header": "Última ejecución", + "backup-database-task": "Copia de seguridad de la base de datos", + "backup-database-task-desc": "Realiza una copia de seguridad de la base de datos, marcadores, temas, portadas subidas manualmente y archivos de configuración.", + "analyze-files-task-success": "El análisis de archivos se ha puesto en cola", + "clear-reading-cache-task-success": "La caché se ha limpiado", + "clean-up-want-to-read-task": "Limpiar Quiero Leer", + "clear-reading-cache-task-desc": "Limpia los archivos de caché de lectura. Esto es útil cuando acabas de actualizar un archivo que habías estado leyendo en las últimas 24 horas.", + "clean-up-want-to-read-task-desc": "Elimina cualquier serie que los usuarios hayan leído por completo, que se encuentre en Quiero Leer y que tenga el estado de publicación Completado. Se inicia cada 24 horas.", + "library-scan-tooltip": "Cada cuánto tiempo Kavita escaneará y recargará los metadatos de los archivos de biblioteca.", + "clean-up-want-to-read-task-success": "Se ha limpiado Quiero Leer", + "convert-media-task": "Convertir multimedia al codificado objetivo", + "convert-media-success": "Se ha puesto en cola la conversión de multimedia al codificado objetivo", + "check-for-updates-task-desc": "Comprobar si hay versiones estables posteriores a tu versión.", + "analyze-files-task-desc": "Inicia una tarea de larga duración que analizará los archivos para detectar extensión y tamaño. Esto solo debería usarse una sola vez en la versión 0.7. No es necesario para versiones posteriores a la 0.7.", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}" + }, + "manage-users": { + "edit-user-tooltip": "Editar", + "pending-title": "Pendiente", + "online-now-tooltip": "En línea ahora", + "change-password-tooltip": "Cambiar contraseña", + "change-password-alt": "Cambiar contraseña de {{user}}", + "setup": "Configuración", + "none": "Ninguno", + "never": "Nunca", + "sharing-title": "Compartiendo:", + "no-data": "No hay otros usuarios.", + "title": "Usuarios activos", + "resend-invite-tooltip": "Reenviar invitación", + "resend-invite-alt": "Reenviar invitación a {{user}}", + "edit-user-alt": "Editar usuario {{user}}", + "delete-user-tooltip": "Borrar usuario", + "delete-user-alt": "Borrar usuario {{user}}", + "invite": "Invitación", + "you-alt": "(Tú)", + "loading": "{{common.loading}}", + "roles-title": "Roles:", + "setup-user-tooltip": "Configurar usuario", + "setup-user-alt": "Configurar usuario {{user}}", + "resend": "Reenviar", + "last-active-title": "Activo por última vez:" + }, + "manga-format-stats": { + "title": "Formato", + "visualisation-label": "Visualización", + "format-header": "Formato" + }, + "publication-status-stats": { + "visualisation-label": "Visualización", + "data-table-label": "Tabla de datos", + "year-header": "Año" + }, + "manage-library": { + "last-scanned-title": "Último escaneo:", + "shared-folders-title": "Carpetas compartidas:", + "type-title": "Tipo:", + "title": "Bibliotecas", + "add-library": "Añadir biblioteca", + "no-data": "No existen bibliotecas. Prueba a crear una.", + "scan-library": "Escanear biblioteca", + "delete-library": "Borrar biblioteca", + "delete-library-by-name": "Borrar {{name}}", + "edit-library": "Editar", + "edit-library-by-name": "Eliminar {{name}}", + "loading": "{{common.loading}}" + }, + "edit-collection-tags": { + "general-tab": "General", + "name-label": "Nombre", + "summary-label": "Resumen", + "title": "Editar colección {{collectionName}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "cover-image-tab": "Imagen de portada", + "promote-label": "Ascender", + "series-tab": "Serie", + "promote-tooltip": "El ascenso significa que todo el servidor puede ver la etiqueta, no solo los administradores. Todas las series que tengan esta etiqueta seguirán teniendo restricciones de acceso establecidas.", + "name-validation": "El nombre debe ser único", + "series-title": "Se aplica a las series", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}" + }, + "library-detail": { + "library-tab": "Biblioteca", + "recommended-tab": "Recomendado" + }, + "server-stats": { + "reads": "lecturas", + "release-years-title": "Años de lanzamiento", + "most-active-users-title": "Usuarios más activos", + "tags": "Etiquetas", + "people": "Personas", + "genres": "Géneros" + }, + "library-recommended": { + "rediscover": "Descubrir de nuevo", + "quick-catchups": "Sigue dónde lo dejaste", + "on-deck": "{{dashboard.on-deck-title}}", + "quick-reads": "Lecturas rápidas", + "no-data": "Nada que ver aquí. Añade metadatos a tu biblioteca, lee algo o valora algo. Esta biblioteca puede que tenga las recomendaciones desactivadas.", + "more-in-genre": "Más en {{genre}}", + "highly-rated": "Mejor valorados" + }, + "draggable-ordered-list": { + "reorder-label": "Ordenar de nuevo", + "remove-item-alt": "Eliminar elemento" + }, + "collection-detail": { + "no-data": "No hay elementos. Prueba a añadir una serie.", + "no-data-filtered": "No hay elementos coincidentes con tu filtro actual.", + "title-alt": "Kavita - Colección {{collectionName}}" + }, + "infinite-scroller": { + "continuous-reading-next-chapter-alt": "Desplázate hacia arriba para ir al capítulo siguiente", + "continuous-reading-prev-chapter-alt": "Desplázate hacia arriba para ir al capítulo anterior", + "continuous-reading-prev-chapter": "Capítulo anterior", + "continuous-reading-next-chapter": "Capítulo siguiente" + }, + "carousel-reel": { + "prev-items": "Elementos anteriores", + "next-items": "Elementos siguientes" } } diff --git a/UI/Web/src/assets/langs/fr.json b/UI/Web/src/assets/langs/fr.json new file mode 100644 index 0000000000..937b015d31 --- /dev/null +++ b/UI/Web/src/assets/langs/fr.json @@ -0,0 +1,1686 @@ +{ + "login": { + "title": "Connexion", + "username": "{{common.username}}", + "password": "{{common.password}}", + "password-validation": "{{validation.password-validation}}", + "forgot-password": "Mot de passe oublié ?", + "submit": "{{common.submit}}" + }, + "dashboard": { + "no-libraries": "Aucune bibliothèque n'est configurée. Veuillez en ajouter une", + "server-settings-link": "Paramètres du serveur", + "not-granted": "Vous n'avez accès à aucune bibliothèque.", + "on-deck-title": "En Cours", + "recently-updated-title": "Séries mises-à-jour récemment", + "recently-added-title": "Séries ajoutées récemment" + }, + "edit-user": { + "edit": "{{common.edit}}", + "close": "{{common.close}}", + "username": "{{common.username}}", + "required": "{{validation.required-field}}", + "email": "{{common.email}}", + "not-valid-email": "{{validation.valid-email}}", + "cancel": "{{common.cancel}}", + "saving": "Sauvegarde…", + "update": "Mise-à-jour" + }, + "user-scrobble-history": { + "title": "Historique de scrobble", + "description": "Ici, vous trouverez les événements de scrobble reliées à votre compte. Afin d'avoir des événements, vous devez avoir un fournisseur de scrobble actif et configuré. Tous les événements qui ont été traités vont automatiquement disparaitre après un mois. Si des événements demeurent non traités, il est fort probable qu'il n'y ait aucune correspondance chez le fournisseur. Veuillez contacter votre administrateur afin de les corriger.", + "filter-label": "Filtre", + "created-header": "Création", + "last-modified-header": "Dernière modification", + "type-header": "Type", + "series-header": "Séries", + "data-header": "Données", + "is-processed-header": "A été traité", + "no-data": "Aucune donnée", + "volume-and-chapter-num": "Volume {{v}} Chapitre {{n}}", + "rating": "Évaluation {{r}}", + "not-applicable": "Non applicable", + "processed": "Traité", + "not-processed": "Non traité" + }, + "scrobble-event-type-pipe": { + "chapter-read": "Progression de la lecture", + "score-updated": "Mise à jour de l'évaluation", + "want-to-read-add": "À lire : Ajouter à la liste", + "want-to-read-remove": "À lire : Retirer de la liste", + "review": "Mettre à jour la critique" + }, + "spoiler": { + "click-to-show": "Divulgâcheur, cliquez afin d'afficher" + }, + "review-series-modal": { + "title": "Éditer la critique", + "tagline-label": "Slogan", + "review-label": "Critique", + "close": "{{common.close}}", + "save": "{{common.save}}" + }, + "review-card-modal": { + "close": "{{common.close}}", + "user-review": "Critique de {{username}}", + "external-mod": "(externe)", + "go-to-review": "Lire la critique" + }, + "review-card": { + "your-review": "Voici votre critique", + "external-review": "Critique externe", + "local-review": "Critique", + "rating-percentage": "Évaluation {{r}}%" + }, + "want-to-read": { + "title": "À Lire", + "series-count": "{{common.series-count}}", + "no-items": "Il n'y a pas d'éléments. Essayez d'ajouter une série.", + "no-items-filtered": "Aucun éléments correspondant aux critères." + }, + "user-preferences": { + "title": "Tableau de bord utilisateur", + "pref-description": "Voici les paramètres globaux liés à votre compte.", + "account-tab": "Compte", + "preferences-tab": "Préférences", + "3rd-party-clients-tab": "Clients tiers", + "theme-tab": "Thème", + "devices-tab": "Appareils", + "stats-tab": "Statistiques", + "scrobbling-tab": "", + "success-toast": "Préférences utilisateur mises à jour", + "global-settings-title": "Paramètres généraux", + "page-layout-mode-label": "Mode de mise en page des pages", + "page-layout-mode-tooltip": "Afficher les éléments sous forme de cartes ou de listes sur la page des détails de la série.", + "locale-label": "Localisation", + "locale-tooltip": "La langue que Kavita devrait utiliser", + "blur-unread-summaries-label": "Flouter les résumés non lus", + "blur-unread-summaries-tooltip": "Le texte du résumé est flouté sur les volumes ou les chapitres dont la lecture n'a pas progressé (afin d'éviter les spoilers)", + "prompt-on-download-label": "Invitation à télécharger", + "prompt-on-download-tooltip": "Message lorsqu'un téléchargement dépasse la taille de {{size}}MB", + "disable-animations-label": "Désactiver les Animations", + "disable-animations-tooltip": "Désactive les animations dans le site. Utile pour les utilisateurs de liseuse (Kindle etc..).", + "collapse-series-relationships-label": "Réduire les relations des séries", + "collapse-series-relationships-tooltip": "Kavita doit-il afficher des Séries qui n'ont pas de lien ou sont le parent/préquel", + "share-series-reviews-label": "Partagez les commentaires de la Série", + "share-series-reviews-tooltip": "Kavita doit-il afficher vos commentaires sur les Séries pour les autres utilisateurs", + "image-reader-settings-title": "Lecteur d'image", + "reading-direction-label": "Sens de lecture", + "reading-direction-tooltip": "Direction dans laquelle cliquer pour passer à la page suivante. De droite à gauche signifie que vous cliquez sur le côté gauche de l'écran pour passer à la page suivante.", + "scaling-option-label": "Options de mise à l'échelle", + "scaling-option-tooltip": "Comment ajuster la taille de l'image a votre écran.", + "page-splitting-label": "Séparation des pages", + "page-splitting-tooltip": "Comment diviser une image pleine largeur (càd que les images de gauche et de droite sont combinées)", + "reading-mode-label": "Mode de lecture", + "layout-mode-label": "Mode de mise en page", + "layout-mode-tooltip": "Rendu d'une seule image à l'écran ou de deux images côte à côte", + "background-color-label": "Couleur d'arrière-plan", + "auto-close-menu-label": "Menu de fermeture automatique", + "show-screen-hints-label": "Afficher les conseils à l'écran", + "emulate-comic-book-label": "Imiter l'aspect comic", + "swipe-to-paginate-label": "Glisser pour paginer", + "book-reader-settings-title": "Lecteur de livre", + "tap-to-paginate-label": "Appuyer pour paginer", + "tap-to-paginate-tooltip": "Les bords d'écran du lecteur de livre doivent-ils permettre d'appuyer pour passer à la page précédente/suivante", + "immersive-mode-label": "Mode immersif", + "immersive-mode-tooltip": "", + "reading-direction-book-label": "Sens de lecture", + "reading-direction-book-tooltip": "Sens du clic pour aller à page suivante. Droite à gauche signifie que vous devez cliquer du côté gauche de l'écran pour aller à la page suivante.", + "font-family-label": "Famille de polices", + "font-family-tooltip": "Famille de police à charger. Par défaut chargera la police par défaut du livre", + "writing-style-label": "Style d'écriture", + "writing-style-tooltip": "Change la direction du texte. Horizontal est de gauche à droite, vertical est de haut en bas.", + "layout-mode-book-label": "Mise en page", + "layout-mode-book-tooltip": "Comment le contenu devrait être mis en page. Défilement correspond à la présentation du livre. 1 ou 2 Colonnes utilise 1 ou 2 colonnes adaptées à la hauteur de l'appareil par page", + "color-theme-book-label": "Thème de couleur", + "color-theme-book-tooltip": "Quelle couleur appliquer aux contenu et menu du lecteur de livre", + "font-size-book-label": "Taille de Police", + "line-height-book-label": "Interligne", + "line-height-book-tooltip": "Quel espace entre les lignes du livre", + "margin-book-label": "Marge", + "margin-book-tooltip": "Quel espace de chaque côté de l'écran. Cela sera remplacé par 0 sur les appareils mobiles indépendamment de ce réglage.", + "clients-opds-alert": "OPDS n'est pas actif sur ce serveur. Cela n'affectera pas les utilisateurs de Tachiyomi.", + "clients-opds-description": "Tous les clients tiers utiliseront soit la Clé d'API ou l'Url de connexion ci-dessous. Ce sont comme des mots de passe, ne les divulguez pas.", + "clients-api-key-tooltip": "La Clé d'API est comme un mot de passe. Gardez-la secrète, gardez-la en sécurité.", + "clients-opds-url-tooltip": "URL OPDS", + "reset": "{{common.reset}}", + "save": "{{common.save}}" + }, + "user-holds": { + "title": "", + "description": "" + }, + "theme-manager": { + "title": "Gestionnaire de Theme", + "looking-for-theme": "Vous cherchez un theme clair ou E-ink? Nous avons quelques themes personalisés que vous pouvez utiliser. ", + "looking-for-theme-continued": "Github des themes.", + "scan": "Analyser", + "site-themes": "Site des Themes", + "set-default": "Remettre choix par défaut", + "apply": "", + "applied": "Appliquer", + "updated-toastr": "Site par défaut a été mise à jour vers {{name}}", + "scan-queued": "" + }, + "theme": { + "theme-dark": "Foncé", + "theme-black": "Noir", + "theme-paper": "Papier", + "theme-white": "Blanc" + }, + "restriction-selector": { + "title": "Restriction d'âge", + "description": "", + "not-applicable-for-admins": "Ceci ne s'applique pas pour les administrateurs.", + "age-rating-label": "Classification d'âge", + "no-restriction": "Aucun restriction", + "include-unknowns-label": "Inclure les Inconnus", + "include-unknowns-tooltip": "" + }, + "site-theme-provider-pipe": { + "system": "Système", + "user": "Utilisateur" + }, + "manage-devices": { + "title": "Gestionnaire de périphérique", + "description": "", + "devices-title": "Périphériques", + "no-devices": "Il n'y a présentement aucun périphérique de configuré", + "platform-label": "Platforme : ", + "email-label": "Courriel : ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" + }, + "edit-device": { + "device-name-label": "Nom du Périphérique", + "email-label": "{{common.email}}", + "email-tooltip": "Cette adresse courriel va être utiliser pour accepter le ficher via Envoyer Vers", + "device-platform-label": "Platforme du périphérique", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" + }, + "change-password": { + "password-label": "{{common.password}}", + "current-password-label": "Mot de passe actuel", + "new-password-label": "Nouveau Mot de Passe", + "confirm-password-label": "Confirmez le mot de passe", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "Les mots de passe doivent être identique", + "permission-error": "Vous n'avez pas la permission de changer votre mot de passe. Contactez l'administrateur du serveur." + }, + "change-email": { + "email-label": "{{common.email}}", + "current-password-label": "Mot de passe actuel", + "email-not-confirmed": "Ce adresse courriel n'a pas été confirmé", + "email-updated-title": "Courriel Mise à jour", + "email-updated-description": "", + "setup-user-account": "Configurer un compte utilisateur", + "invite-url-label": "Url d'invitation", + "invite-url-tooltip": "Copier ceci et coller dans un nouvel onglet", + "permission-error": "Vous n'avez pas la permission de changer votre adresse courriel. Contacter l'administrateur du serveur.", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "change-age-restriction": { + "age-restriction-label": "Restriction d'âge", + "unknowns": "Inconnus", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "api-key": { + "copy": "Copier", + "regen-warning": "La regénération de votre clé API va invalider tout les clients existants.", + "no-key": "Erreur - La clé n'a pas été configuré", + "confirm-reset": "", + "key-reset": "Remise à zéro de la clé API", + "show": "Afficher" + }, + "scrobbling-providers": { + "title": "", + "requires": "Ce fonctionnalité requiert un licence active du {{product}}", + "token-expired": "Jeton expiré", + "no-token-set": "Aucun jeton n'a été configuré", + "token-set": "Jeton à été configuré", + "generate": "Générer", + "instructions": "", + "token-input-label": "", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "typeahead": { + "locked-field": "Ce champ est vérouillé", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "add-item": "Ajouter {{item}}…", + "no-data": "Aucune donnée", + "add-custom-item": ", taper pour ajouter un objet personnalisé" + }, + "generic-list-modal": { + "close": "{{common.close}}", + "clear": "Effacer", + "filter": "Filtrer", + "open-filtered-search": "Ouvrez une recherche filtrée {{item}}" + }, + "user-stats-info-cards": { + "total-pages-read-label": "Total de pages lues", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}} : {{value}}", + "total-words-read-label": "Nombre Total de Mots Lus", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "Temps passé à lire", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "Chapitres Lu", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "avg-reading-per-week-label": "Lecture moyenne / semaine", + "last-active-label": "Dernière fois en ligne", + "chapters": "{{value}} chapitres" + }, + "user-stats": { + "library-read-progress-title": "Progrès de lecture de la bibliothèque", + "read-percentage": "% Lu" + }, + "top-readers": { + "title": "Meilleurs Lecteurs", + "time-selection-label": "Laps de temps", + "comics-label": "BD: {{value}} hrs", + "manga-label": "Manga: {{value}} hrs", + "books-label": "Livres: {{value}} hrs", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" + }, + "role-selector": { + "title": "Rôles" + }, + "directory-picker": { + "title": "Sélectionner un Dossier", + "close": "{{common.close}}", + "path-label": "Chemin", + "path-placeholder": "Commencer à écrire ou choisir le chemin", + "instructions": "", + "type-header": "Type", + "name-header": "Nom", + "cancel": "{{common.cancel}}", + "share": "Partager", + "help": "{{common.help}}" + }, + "library-access-modal": { + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "Accès à la Bibliothèque", + "close": "{{common.close}}", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "no-data": "Il n'y a présentement aucune bibliothèque de configurer." + }, + "time-periods": { + "this-week": "Cette Semaine", + "last-7-days": "7 derniers jours", + "last-30-days": "30 derniers jours", + "last-90-days": "90 derniers jours", + "last-year": "L'année dernière", + "all-time": "En tout Temps" + }, + "device-platform-pipe": { + "custom": "Personnalisé" + }, + "day-of-week-pipe": { + "monday": "Lundi", + "tuesday": "Mardi", + "wednesday": "Mercredi", + "thursday": "Jeudi", + "friday": "Vendredi", + "saturday": "Samedi", + "sunday": "Dimanche" + }, + "cbl-import-result-pipe": { + "success": "Succès", + "partial": "Partiel", + "failure": "Échec" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "", + "chapter-missing": "", + "empty-file": "Le fichier cbl est vide, rien à faire.", + "name-conflict": "", + "series-collision": "", + "series-missing": "", + "volume-missing": "", + "all-chapter-missing": "", + "invalid-file": "", + "success": "" + }, + "time-duration-pipe": { + "hours": "", + "minutes": "", + "days": "", + "months": "", + "years": "" + }, + "time-ago-pipe": { + "never": "", + "just-now": "", + "min-ago": "", + "mins-ago": "", + "hour-ago": "", + "hours-ago": "", + "day-ago": "", + "days-ago": "", + "month-ago": "", + "months-ago": "", + "year-ago": "", + "years-ago": "" + }, + "relationship-pipe": { + "adaptation": "", + "alternative-setting": "", + "alternative-version": "", + "character": "", + "contains": "", + "doujinshi": "", + "other": "", + "prequel": "", + "sequel": "", + "side-story": "", + "spin-off": "", + "parent": "", + "edition": "" + }, + "publication-status-pipe": { + "ongoing": "", + "hiatus": "", + "completed": "", + "cancelled": "", + "ended": "" + }, + "person-role-pipe": { + "artist": "", + "character": "", + "colorist": "", + "cover-artist": "", + "editor": "", + "inker": "", + "letterer": "", + "penciller": "", + "publisher": "", + "writer": "", + "other": "" + }, + "manga-format-pipe": { + "epub": "", + "archive": "", + "image": "", + "pdf": "", + "unknown": "" + }, + "library-type-pipe": { + "book": "", + "comic": "", + "manga": "" + }, + "age-rating-pipe": { + "unknown": "", + "early-childhood": "", + "adults-only": "", + "everyone": "", + "everyone-10-plus": "", + "g": "", + "kids-to-adults": "", + "mature": "", + "ma15-plus": "", + "mature-17-plus": "", + "rating-pending": "", + "teen": "", + "x18-plus": "", + "not-applicable": "", + "pg": "", + "r18-plus": "" + }, + "reset-password": { + "title": "", + "description": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "submit": "" + }, + "reset-password-modal": { + "title": "", + "new-password-label": "", + "error-label": "", + "close": "", + "cancel": "", + "save": "" + }, + "all-series": { + "series-count": "", + "title": "Toutes les Séries" + }, + "announcements": { + "title": "" + }, + "changelog": { + "installed": "", + "download": "", + "published-label": "", + "available": "", + "description": "", + "description-continued": "" + }, + "invite-user": { + "title": "", + "close": "", + "description": "", + "email": "", + "required-field": "", + "setup-user-title": "", + "setup-user-description": "", + "setup-user-account": "", + "setup-user-account-tooltip": "", + "invite-url-label": "", + "invite": "", + "inviting": "", + "cancel": "" + }, + "library-selector": { + "title": "", + "select-all": "", + "deselect-all": "", + "no-data": "" + }, + "license": { + "title": "", + "manage": "", + "invalid-license-tooltip": "", + "check": "", + "cancel": "", + "edit": "", + "buy": "", + "activate": "", + "renew": "", + "no-license-key": "", + "license-valid": "", + "license-not-valid": "", + "loading": "", + "activate-description": "", + "activate-license-label": "", + "activate-email-label": "", + "activate-delete": "", + "activate-save": "" + }, + "book-line-overlay": { + "copy": "", + "bookmark": "", + "close": "", + "required-field": "", + "bookmark-label": "", + "save": "" + }, + "book-reader": { + "title": "", + "page-label": "", + "pagination-header": "", + "go-to-page": "", + "go-to-last-page": "", + "prev-page": "", + "next-page": "", + "prev-chapter": "", + "next-chapter": "", + "skip-header": "", + "virtual-pages": "", + "settings-header": "", + "table-of-contents-header": "", + "bookmarks-header": "", + "toc-header": "", + "loading-book": "", + "go-back": "", + "incognito-mode-alt": "", + "incognito-mode-label": "", + "next": "", + "previous": "", + "go-to-page-prompt": "" + }, + "personal-table-of-contents": { + "no-data": "", + "page": "", + "delete": "" + }, + "confirm-email": { + "title": "", + "description": "", + "error-label": "", + "username-label": "", + "password-label": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "confirm-email-change": { + "title": "", + "non-confirm-description": "", + "confirm-description": "", + "success": "" + }, + "confirm-reset-password": { + "title": "", + "description": "", + "password-label": "", + "required-field": "", + "submit": "", + "password-validation": "" + }, + "register": { + "title": "", + "description": "", + "username-label": "", + "email-label": "", + "email-tooltip": "", + "password-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "series-detail": { + "page-settings-title": "", + "close": "", + "layout-mode-label": "", + "layout-mode-option-card": "", + "layout-mode-option-list": "", + "continue-from": "", + "read": "", + "continue": "", + "read-options-alt": "", + "incognito": "", + "remove-from-want-to-read": "", + "add-to-want-to-read": "", + "edit-series-alt": "", + "download-series--tooltip": "", + "downloading-status": "", + "user-reviews-alt": "", + "storyline-tab": "", + "books-tab": "", + "volumes-tab": "", + "specials-tab": "", + "related-tab": "", + "recommendations-tab": "", + "send-to": "", + "no-pages": "", + "no-chapters": "", + "cover-change": "" + }, + "series-metadata-detail": { + "links-title": "", + "genres-title": "", + "tags-title": "", + "collections-title": "{{side-nav.collections}}", + "reading-lists-title": "", + "writers-title": "", + "cover-artists-title": "", + "characters-title": "", + "colorists-title": "", + "editors-title": "", + "inkers-title": "", + "letterers-title": "", + "translators-title": "", + "pencillers-title": "", + "publishers-title": "", + "promoted": "", + "see-more": "", + "see-less": "" + }, + "badge-expander": { + "more-items": "" + }, + "read-more": { + "read-more": "", + "read-less": "" + }, + "update-notification-modal": { + "title": "", + "close": "", + "help": "", + "download": "" + }, + "side-nav-companion-bar": { + "page-settings-title": "", + "open-filter-and-sort": "", + "close-filter-and-sort": "", + "filter-and-sort-alt": "" + }, + "side-nav": { + "home": "Accueil", + "want-to-read": "À Lire", + "collections": "Collections", + "reading-lists": "Listes de lecture", + "bookmarks": "Marque-pages", + "filter-label": "", + "all-series": "Toutes les Séries", + "clear": "", + "donate": "" + }, + "library-settings-modal": { + "close": "", + "edit-title": "", + "add-title": "", + "general-tab": "", + "folder-tab": "", + "cover-tab": "", + "advanced-tab": "", + "name-label": "", + "library-name-unique": "", + "last-scanned-label": "", + "type-label": "", + "type-tooltip": "", + "folder-description": "", + "browse": "", + "help-us-part-1": "", + "help-us-part-2": "", + "help-us-part-3": "", + "naming-conventions-part-1": "", + "naming-conventions-part-2": "", + "naming-conventions-part-3": "", + "cover-description": "", + "cover-description-extra": "", + "manage-collection-label": "", + "manage-collection-tooltip": "", + "manage-reading-list-label": "", + "manage-reading-list-tooltip": "", + "allow-scrobbling-label": "", + "allow-scrobbling-tooltip": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "include-in-dashboard-label": "", + "include-in-dashboard-tooltip": "", + "include-in-recommendation-label": "", + "include-in-recommendation-tooltip": "", + "include-in-search-label": "", + "include-in-search-tooltip": "", + "force-scan": "", + "force-scan-tooltip": "", + "reset": "", + "cancel": "", + "next": "", + "save": "", + "required-field": "" + }, + "reader-settings": { + "general-settings-title": "", + "font-family-label": "", + "font-size-label": "", + "line-spacing-label": "", + "margin-label": "", + "reset-to-defaults": "", + "reader-settings-title": "", + "reading-direction-label": "", + "right-to-left": "", + "left-to-right": "", + "horizontal": "", + "vertical": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "on": "", + "off": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "fullscreen-label": "", + "fullscreen-tooltip": "", + "exit": "", + "enter": "", + "layout-mode-label": "", + "layout-mode-tooltip": "", + "layout-mode-option-scroll": "", + "layout-mode-option-1col": "", + "layout-mode-option-2col": "", + "color-theme-title": "", + "theme-dark": "", + "theme-black": "", + "theme-white": "", + "theme-paper": "" + }, + "table-of-contents": { + "no-data": "" + }, + "bookmarks": { + "title": "", + "series-count": "", + "no-data": "", + "no-data-2": "", + "confirm-delete": "", + "confirm-single-delete": "", + "delete-success": "", + "delete-single-success": "" + }, + "bulk-operations": { + "title": "", + "items-selected": "", + "mark-as-unread": "", + "mark-as-read": "", + "deselect-all": "" + }, + "card-detail-drawer": { + "general-tab": "", + "metadata-tab": "", + "cover-tab": "", + "info-tab": "", + "no-summary": "", + "writers-title": "", + "genres-title": "", + "publishers-title": "", + "tags-title": "", + "not-defined": "", + "read": "", + "unread": "", + "files": "", + "pages": "", + "added": "", + "size": "" + }, + "card-detail-layout": { + "total-items": "" + }, + "card-item": { + "cannot-read": "" + }, + "chapter-metadata-detail": { + "no-data": "", + "writers-title": "", + "publishers-title": "", + "characters-title": "", + "translators-title": "", + "letterers-title": "", + "colorists-title": "", + "inkers-title": "", + "pencillers-title": "", + "cover-artists-title": "", + "editors-title": "" + }, + "cover-image-chooser": { + "drag-n-drop": "", + "upload": "", + "upload-continued": "", + "url-label": "", + "load": "", + "back": "", + "reset-cover-tooltip": "", + "reset": "", + "image-num": "", + "apply": "", + "applied": "" + }, + "download-indicator": { + "progress": "" + }, + "edit-series-relation": { + "description-part-1": "", + "description-part-2": "", + "target-series": "", + "relationship": "", + "remove": "", + "add-relationship": "", + "parent": "" + }, + "entity-info-cards": { + "tags-title": "", + "characters-title": "", + "release-date-title": "", + "release-date-tooltip": "", + "age-rating-title": "", + "length-title": "", + "pages-count": "", + "words-count": "", + "reading-time-title": "", + "date-added-title": "", + "size-title": "", + "id-title": "", + "links-title": "", + "isbn-title": "", + "last-read-title": "", + "less-than-hour": "", + "range-hours": "", + "hour": "", + "hours": "", + "read-time-title": "" + }, + "series-info-cards": { + "release-date-title": "", + "release-year-tooltip": "", + "age-rating-title": "", + "language-title": "", + "publication-status-title": "", + "publication-status-tooltip": "", + "scrobbling-title": "", + "scrobbling-tooltip": "", + "on": "", + "off": "", + "disabled": "", + "format-title": "", + "last-read-title": "", + "length-title": "", + "read-time-title": "", + "less-than-hour": "", + "hour": "", + "hours": "", + "time-left-title": "", + "ongoing": "", + "pages-count": "", + "words-count": "" + }, + "bulk-add-to-collection": { + "title": "", + "promoted": "", + "close": "", + "filter-label": "", + "clear": "", + "no-data": "", + "loading": "", + "collection-label": "", + "create": "" + }, + "entity-title": { + "special": "", + "issue-num": "", + "chapter": "" + }, + "external-series-card": { + "open-external": "" + }, + "list-item": { + "read": "" + }, + "manage-alerts": { + "description-part-1": "", + "description-part-2": "", + "filter-label": "", + "clear-alerts": "", + "extension-header": "", + "file-header": "", + "comment-header": "", + "details-header": "" + }, + "manage-email-settings": { + "title": "", + "description": "", + "send-to-warning": "", + "email-url-label": "", + "email-url-tooltip": "", + "reset": "", + "test": "", + "host-name-label": "", + "host-name-tooltip": "", + "host-name-validation": "", + "reset-to-default": "", + "save": "" + }, + "manage-library": { + "title": "", + "add-library": "", + "no-data": "", + "loading": "", + "last-scanned-title": "", + "shared-folders-title": "", + "type-title": "", + "scan-library": "", + "delete-library": "", + "delete-library-by-name": "", + "edit-library": "", + "edit-library-by-name": "" + }, + "manage-media-settings": { + "encode-as-description-part-1": "", + "encode-as-description-part-2": "", + "encode-as-description-part-3": "", + "encode-as-warning": "", + "media-warning": "", + "encode-as-label": "", + "encode-as-tooltip": "", + "bookmark-dir-label": "", + "bookmark-dir-tooltip": "", + "change": "", + "reset-to-default": "", + "reset": "", + "save": "", + "media-issue-title": "", + "scrobble-issue-title": "" + }, + "manage-scrobble-errors": { + "description": "", + "filter-label": "", + "clear-errors": "", + "series-header": "", + "created-header": "", + "comment-header": "", + "edit-header": "", + "edit-item-alt": "" + }, + "default-date-pipe": { + "never": "" + }, + "manage-settings": { + "notice": "", + "restart-required": "", + "base-url-label": "", + "base-url-tooltip": "", + "ip-address-label": "", + "ip-address-tooltip": "", + "port-label": "", + "port-tooltip": "", + "backup-label": "", + "backup-tooltip": "", + "log-label": "", + "log-tooltip": "", + "logging-level-label": "", + "logging-level-tooltip": "", + "cache-size-label": "", + "cache-size-tooltip": "", + "on-deck-last-progress-label": "", + "on-deck-last-progress-tooltip": "", + "on-deck-last-chapter-add-label": "", + "on-deck-last-chapter-add-tooltip": "", + "allow-stats-label": "", + "allow-stats-tooltip-part-1": "", + "allow-stats-tooltip-part-2": "", + "send-data": "", + "opds-label": "", + "opds-tooltip": "", + "enable-opds": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "enable-folder-watching": "", + "reset-to-default": "", + "reset": "", + "save": "", + "cache-size-validation": "", + "field-required": "", + "max-logs-validation": "", + "min-logs-validation": "", + "min-days-validation": "", + "min-cache-validation": "", + "max-backup-validation": "", + "min-backup-validation": "", + "ip-address-validation": "", + "base-url-validation": "" + }, + "manage-system": { + "title": "", + "version-title": "", + "installId-title": "", + "more-info-title": "", + "home-page-title": "", + "wiki-title": "", + "discord-title": "", + "donations-title": "", + "source-title": "", + "feature-request-title": "" + }, + "manage-tasks-settings": { + "title": "", + "library-scan-label": "", + "library-scan-tooltip": "", + "library-database-backup-label": "", + "library-database-backup-tooltip": "", + "adhoc-tasks-title": "", + "job-title-header": "", + "description-header": "", + "action-header": "", + "reset-to-default": "", + "reset": "", + "save": "", + "recurring-tasks-title": "", + "last-executed-header": "", + "cron-header": "", + "convert-media-task": "", + "convert-media-task-desc": "", + "convert-media-success": "", + "bust-cache-task": "", + "bust-cache-task-desc": "", + "bust-cache-task-success": "", + "clear-reading-cache-task": "", + "clear-reading-cache-task-desc": "", + "clear-reading-cache-task-success": "", + "clean-up-want-to-read-task": "", + "clean-up-want-to-read-task-desc": "", + "clean-up-want-to-read-task-success": "", + "backup-database-task": "", + "backup-database-task-desc": "", + "backup-database-task-success": "", + "download-logs-task": "", + "download-logs-task-desc": "", + "analyze-files-task": "", + "analyze-files-task-desc": "", + "analyze-files-task-success": "", + "check-for-updates-task": "", + "check-for-updates-task-desc": "" + }, + "manage-users": { + "title": "", + "invite": "", + "you-alt": "", + "pending-title": "", + "delete-user-tooltip": "", + "delete-user-alt": "", + "edit-user-tooltip": "", + "edit-user-alt": "", + "resend-invite-tooltip": "", + "resend-invite-alt": "", + "setup-user-tooltip": "", + "setup-user-alt": "", + "change-password-tooltip": "", + "change-password-alt": "", + "resend": "", + "setup": "", + "last-active-title": "", + "roles-title": "", + "none": "", + "never": "", + "online-now-tooltip": "", + "sharing-title": "", + "no-data": "", + "loading": "" + }, + "edit-collection-tags": { + "title": "", + "required-field": "", + "save": "", + "close": "", + "cancel": "", + "general-tab": "", + "cover-image-tab": "", + "series-tab": "", + "name-label": "", + "name-validation": "", + "promote-label": "", + "promote-tooltip": "", + "summary-label": "", + "series-title": "", + "deselect-all": "", + "select-all": "" + }, + "library-detail": { + "library-tab": "", + "recommended-tab": "" + }, + "library-recommended": { + "no-data": "", + "more-in-genre": "", + "rediscover": "", + "highly-rated": "", + "quick-catchups": "", + "quick-reads": "", + "on-deck": "" + }, + "admin-dashboard": { + "title": "", + "general-tab": "", + "users-tab": "", + "libraries-tab": "", + "media-tab": "", + "logs-tab": "", + "email-tab": "", + "tasks-tab": "", + "statistics-tab": "", + "system-tab": "", + "kavita+-tab": "", + "kavita+-desc-part-1": "", + "kavita+-desc-part-2": "", + "kavita+-desc-part-3": "" + }, + "collection-detail": { + "no-data": "", + "no-data-filtered": "", + "title-alt": "" + }, + "all-collections": { + "title": "Collections", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "carousel-reel": { + "prev-items": "", + "next-items": "" + }, + "draggable-ordered-list": { + "instructions-alt": "", + "reorder-label": "", + "remove-item-alt": "" + }, + "reading-lists": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "reading-list-item": { + "remove": "", + "read": "" + }, + "reading-list-detail": { + "item-count": "", + "page-settings-title": "", + "remove-read": "", + "order-numbers-label": "", + "continue": "", + "read": "", + "read-options-alt": "", + "incognito-alt": "", + "no-data": "" + }, + "events-widget": { + "title-alt": "", + "dismiss-all": "", + "update-available": "", + "downloading-item": "", + "more-info": "", + "close": "", + "users-online-count": "", + "active-events-title": "", + "no-data": "" + }, + "shortcuts-modal": { + "title": "", + "close": "", + "prev-page": "", + "next-page": "", + "go-to": "", + "bookmark": "", + "double-click": "", + "close-reader": "", + "toggle-menu": "" + }, + "grouped-typeahead": { + "files": "", + "chapters": "", + "people": "", + "tags": "", + "genres": "", + "libraries": "", + "reading-lists": "", + "collections": "Collections", + "close": "", + "loading": "" + }, + "nav-header": { + "skip-alt": "", + "search-series-alt": "", + "search-alt": "", + "promoted": "", + "no-data": "", + "scroll-to-top-alt": "", + "server-settings": "", + "settings": "", + "help": "", + "announcements": "", + "logout": "" + }, + "add-to-list-modal": { + "title": "", + "close": "", + "filter-label": "", + "promoted-alt": "", + "no-data": "", + "loading": "", + "reading-list-label": "", + "create": "" + }, + "edit-reading-list-modal": { + "title": "", + "general-tab": "", + "cover-image-tab": "", + "close": "", + "save": "", + "year-validation": "", + "month-validation": "", + "name-unique-validation": "", + "required-field": "", + "summary-label": "", + "year-label": "", + "month-label": "", + "ending-title": "", + "starting-title": "", + "promote-label": "", + "promote-tooltip": "" + }, + "import-cbl-modal": { + "close": "", + "title": "", + "import-description": "", + "validate-description": "", + "validate-warning": "", + "validate-no-issue": "", + "validate-no-issue-description": "", + "dry-run-description": "", + "prev": "", + "import": "", + "restart": "", + "next": "", + "import-step": "", + "validate-cbl-step": "", + "dry-run-step": "", + "final-import-step": "" + }, + "pdf-reader": { + "loading-message": "", + "incognito-mode": "", + "light-theme-alt": "", + "dark-theme-alt": "", + "close-reader-alt": "" + }, + "manga-reader": { + "back": "", + "save-globally": "", + "incognito-alt": "", + "incognito-title": "", + "shortcuts-menu-alt": "", + "prev-page-tooltip": "", + "next-page-tooltip": "", + "prev-chapter-tooltip": "", + "next-chapter-tooltip": "", + "first-page-tooltip": "", + "last-page-tooltip": "", + "left-to-right-alt": "", + "right-to-left-alt": "", + "reading-direction-tooltip": "", + "reading-mode-tooltip": "", + "collapse": "", + "fullscreen": "", + "settings-tooltip": "", + "image-splitting-label": "", + "image-scaling-label": "", + "height": "", + "width": "", + "original": "", + "auto-close-menu-label": "", + "swipe-enabled-label": "", + "enable-comic-book-label": "", + "brightness-label": "", + "first-time-reading-manga": "", + "layout-mode-switched": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "user-preferences-updated": "", + "emulate-comic-book-label": "" + }, + "metadata-filter": { + "filter-title": "", + "format-label": "", + "libraries-label": "", + "collections-label": "Collections", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "read-progress-label": "", + "unread": "", + "read": "", + "in-progress": "", + "rating-label": "", + "age-rating-label": "", + "language-label": "", + "publication-status-label": "", + "series-name-label": "", + "series-name-tooltip": "", + "release-label": "", + "min": "", + "max": "", + "sort-by-label": "", + "ascending-alt": "", + "descending-alt": "", + "reset": "", + "apply": "" + }, + "sort-field-pipe": { + "sort-name": "", + "created": "", + "last-modified": "", + "last-chapter-added": "", + "time-to-read": "", + "release-year": "" + }, + "edit-series-modal": { + "title": "", + "general-tab": "", + "metadata-tab": "", + "people-tab": "", + "web-links-tab": "", + "cover-image-tab": "", + "related-tab": "", + "info-tab": "", + "collections-label": "Collections", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "language-label": "", + "age-rating-label": "", + "publication-status-label": "", + "required-field": "", + "close": "", + "name-label": "", + "sort-name-label": "", + "localized-name-label": "", + "summary-label": "", + "release-year-label": "", + "web-link-description": "", + "web-link-label": "", + "add-link-alt": "", + "remove-link-alt": "", + "cover-image-description": "", + "save": "", + "field-locked-alt": "", + "info-title": "", + "library-title": "", + "format-title": "", + "created-title": "", + "last-read-title": "", + "last-added-title": "", + "last-scanned-title": "", + "folder-path-title": "", + "publication-status-title": "", + "total-pages-title": "", + "total-items-title": "", + "max-items-title": "", + "size-title": "", + "loading": "", + "added-title": "", + "last-modified-title": "", + "view-files": "", + "pages-title": "", + "chapter-title": "", + "volume-num": "", + "highest-count-tooltip": "", + "max-issue-tooltip": "" + }, + "day-breakdown": { + "title": "", + "x-axis-label": "", + "y-axis-label": "" + }, + "file-breakdown-stats": { + "format-title": "", + "format-tooltip": "", + "visualisation-label": "", + "data-table-label": "", + "extension-header": "", + "format-header": "", + "total-size-header": "", + "total-files-header": "", + "not-classified": "", + "total-file-size-title": "" + }, + "reading-activity": { + "title": "", + "legend-label": "", + "x-axis-label": "", + "y-axis-label": "", + "no-data": "", + "time-frame-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "manga-format-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "format-header": "", + "count-header": "" + }, + "publication-status-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "year-header": "", + "count-header": "" + }, + "server-stats": { + "total-series-label": "", + "total-series-tooltip": "", + "total-volumes-label": "", + "total-volumes-tooltip": "", + "total-files-label": "", + "total-files-tooltip": "", + "total-size-label": "", + "total-genres-label": "", + "total-genres-tooltip": "", + "total-tags-label": "", + "total-tags-tooltip": "", + "total-people-label": "", + "total-people-tooltip": "", + "total-read-time-label": "", + "total-read-time-tooltip": "", + "series": "", + "reads": "", + "release-years-title": "", + "most-active-users-title": "", + "popular-libraries-title": "", + "popular-series-title": "", + "recently-read-title": "", + "genre-count": "", + "tag-count": "", + "people-count": "", + "tags": "", + "people": "", + "genres": "" + }, + "errors": { + "series-doesnt-exist": "", + "collection-invalid-access": "", + "unknown-crit": "", + "user-not-auth": "", + "error-code": "", + "download": "", + "not-found": "", + "generic": "", + "rejected-cover-upload": "", + "invalid-confirmation-url": "", + "invalid-confirmation-email": "", + "invalid-password-reset-url": "" + }, + "toasts": { + "regen-cover": "", + "no-pages": "", + "download-in-progress": "", + "scan-queued": "", + "server-settings-updated": "", + "reset-ip-address": "", + "reset-base-url": "", + "unauthorized-1": "", + "unauthorized-2": "", + "no-updates": "", + "confirm-delete-user": "", + "user-deleted": "", + "email-sent-to-user": "", + "click-email-link": "", + "series-added-to-collection": "", + "no-series-collection-warning": "", + "collection-updated": "", + "reading-list-deleted": "", + "reading-list-updated": "", + "confirm-delete-reading-list": "", + "item-removed": "", + "nothing-to-remove": "", + "series-added-to-reading-list": "", + "volumes-added-to-reading-list": "", + "chapter-added-to-reading-list": "", + "multiple-added-to-reading-list": "", + "select-files-warning": "", + "reading-list-imported": "", + "incognito-off": "", + "email-service-reset": "", + "email-service-reachable": "", + "email-service-unresponsive": "", + "refresh-covers-queued": "", + "library-file-analysis-queued": "", + "entity-read": "", + "entity-unread": "", + "mark-read": "", + "mark-unread": "", + "series-removed-want-to-read": "", + "series-deleted": "", + "file-send-to": "", + "theme-missing": "", + "email-sent": "", + "k+-license-saved": "", + "k+-unlocked": "", + "k+-error": "", + "k+-delete-key": "", + "library-deleted": "", + "copied-to-clipboard": "", + "book-settings-info": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "load-next-chapter": "", + "load-prev-chapter": "", + "account-registration-complete": "", + "account-migration-complete": "", + "password-reset": "", + "password-updated": "", + "forced-scan-queued": "", + "library-created": "", + "anilist-token-updated": "", + "age-restriction-updated": "", + "email-sent-to-no-existing": "", + "email-sent-to": "", + "change-email-private": "", + "device-updated": "", + "device-created": "", + "confirm-regen-covers": "", + "alert-long-running": "", + "confirm-delete-multiple-series": "", + "confirm-delete-series": "", + "alert-bad-theme": "", + "confirm-library-delete": "", + "confirm-library-type-change": "", + "confirm-download-size": "" + }, + "actionable": { + "scan-library": "", + "refresh-covers": "", + "analyze-files": "", + "settings": "", + "edit": "", + "mark-as-read": "", + "mark-as-unread": "", + "scan-series": "", + "add-to": "", + "add-to-want-to-read": "", + "remove-from-want-to-read": "", + "remove-from-on-deck": "", + "others": "", + "add-to-reading-list": "", + "add-to-collection": "", + "send-to": "", + "delete": "", + "download": "", + "read-incognito": "", + "details": "", + "view-series": "", + "clear": "", + "import-cbl": "", + "read": "" + }, + "preferences": { + "left-to-right": "", + "right-to-left": "", + "horizontal": "", + "vertical": "", + "automatic": "", + "fit-to-height": "", + "fit-to-width": "", + "original": "", + "fit-to-screen": "", + "no-split": "", + "webtoon": "", + "single": "", + "double": "", + "double-manga": "", + "scroll": "", + "1-column": "", + "2-column": "", + "cards": "", + "list": "", + "up-to-down": "" + }, + "validation": { + "required-field": "", + "valid-email": "", + "password-validation": "" + }, + "entity-type": { + "volume": "", + "chapter": "", + "series": "", + "bookmark": "", + "logs": "" + }, + "common": { + "reset-to-default": "Réinitialiser aux valeurs par défaut", + "close": "Fermer", + "cancel": "Annuler", + "create": "Créer", + "save": "Enregistrer", + "reset": "Réinitialiser", + "add": "Ajouter", + "apply": "Appliquer", + "delete": "Supprimer", + "edit": "Modifier", + "help": "Aide", + "submit": "Soumettre", + "email": "Email", + "read": "Lire", + "loading": "Chargement…", + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "promoted": "", + "select-all": "Tout sélectionner", + "deselect-all": "Tout déselectionner", + "series-count": "{{num}} Séries", + "item-count": "{{num}} Éléments", + "book-num": "Livre", + "issue-hash-num": "Numéro #", + "issue-num": "Numéro", + "chapter-num": "Chapitre", + "volume-num": "Volume" + } +} diff --git a/UI/Web/src/assets/langs/hi.json b/UI/Web/src/assets/langs/hi.json new file mode 100644 index 0000000000..50d78be4d3 --- /dev/null +++ b/UI/Web/src/assets/langs/hi.json @@ -0,0 +1,1684 @@ +{ + "login": { + "title": "लॉग इन करें", + "username": "", + "password": "", + "password-validation": "", + "forgot-password": "", + "submit": "" + }, + "dashboard": { + "no-libraries": "", + "server-settings-link": "", + "not-granted": "", + "on-deck-title": "", + "recently-updated-title": "", + "recently-added-title": "" + }, + "edit-user": { + "edit": "", + "close": "", + "username": "", + "required": "", + "email": "", + "not-valid-email": "", + "cancel": "", + "saving": "", + "update": "" + }, + "user-scrobble-history": { + "title": "", + "description": "", + "filter-label": "", + "created-header": "", + "last-modified-header": "", + "type-header": "", + "series-header": "", + "data-header": "", + "is-processed-header": "", + "no-data": "", + "volume-and-chapter-num": "", + "rating": "", + "not-applicable": "", + "processed": "", + "not-processed": "" + }, + "scrobble-event-type-pipe": { + "chapter-read": "", + "score-updated": "", + "want-to-read-add": "", + "want-to-read-remove": "", + "review": "" + }, + "spoiler": { + "click-to-show": "" + }, + "review-series-modal": { + "title": "", + "tagline-label": "", + "review-label": "", + "close": "", + "save": "" + }, + "review-card-modal": { + "close": "", + "user-review": "", + "external-mod": "", + "go-to-review": "" + }, + "review-card": { + "your-review": "", + "external-review": "", + "local-review": "", + "rating-percentage": "" + }, + "want-to-read": { + "title": "", + "series-count": "", + "no-items": "", + "no-items-filtered": "" + }, + "user-preferences": { + "title": "", + "pref-description": "", + "account-tab": "", + "preferences-tab": "", + "3rd-party-clients-tab": "", + "theme-tab": "", + "devices-tab": "", + "stats-tab": "", + "scrobbling-tab": "", + "success-toast": "", + "global-settings-title": "", + "page-layout-mode-label": "", + "page-layout-mode-tooltip": "", + "locale-label": "", + "locale-tooltip": "", + "blur-unread-summaries-label": "", + "blur-unread-summaries-tooltip": "", + "prompt-on-download-label": "", + "prompt-on-download-tooltip": "", + "disable-animations-label": "", + "disable-animations-tooltip": "", + "collapse-series-relationships-label": "", + "collapse-series-relationships-tooltip": "", + "share-series-reviews-label": "", + "share-series-reviews-tooltip": "", + "image-reader-settings-title": "", + "reading-direction-label": "", + "reading-direction-tooltip": "", + "scaling-option-label": "", + "scaling-option-tooltip": "", + "page-splitting-label": "", + "page-splitting-tooltip": "", + "reading-mode-label": "", + "layout-mode-label": "", + "layout-mode-tooltip": "", + "background-color-label": "", + "auto-close-menu-label": "", + "show-screen-hints-label": "", + "emulate-comic-book-label": "", + "swipe-to-paginate-label": "", + "book-reader-settings-title": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "reading-direction-book-label": "", + "reading-direction-book-tooltip": "", + "font-family-label": "", + "font-family-tooltip": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "layout-mode-book-label": "", + "layout-mode-book-tooltip": "", + "color-theme-book-label": "", + "color-theme-book-tooltip": "", + "font-size-book-label": "", + "line-height-book-label": "", + "line-height-book-tooltip": "", + "margin-book-label": "", + "margin-book-tooltip": "", + "clients-opds-alert": "", + "clients-opds-description": "", + "clients-api-key-tooltip": "", + "clients-opds-url-tooltip": "", + "reset": "", + "save": "" + }, + "user-holds": { + "title": "", + "description": "" + }, + "theme-manager": { + "title": "", + "looking-for-theme": "", + "looking-for-theme-continued": "", + "scan": "", + "site-themes": "", + "set-default": "", + "apply": "", + "applied": "", + "updated-toastr": "", + "scan-queued": "" + }, + "theme": { + "theme-dark": "", + "theme-black": "", + "theme-paper": "", + "theme-white": "" + }, + "restriction-selector": { + "title": "", + "description": "", + "not-applicable-for-admins": "", + "age-rating-label": "", + "no-restriction": "", + "include-unknowns-label": "", + "include-unknowns-tooltip": "" + }, + "site-theme-provider-pipe": { + "system": "", + "user": "" + }, + "manage-devices": { + "title": "", + "description": "", + "devices-title": "", + "no-devices": "", + "platform-label": "", + "email-label": "", + "add": "", + "delete": "", + "edit": "" + }, + "edit-device": { + "device-name-label": "", + "email-label": "", + "email-tooltip": "", + "device-platform-label": "", + "save": "", + "required-field": "", + "valid-email": "" + }, + "change-password": { + "password-label": "", + "current-password-label": "", + "new-password-label": "", + "confirm-password-label": "", + "reset": "", + "edit": "", + "cancel": "", + "save": "", + "required-field": "", + "passwords-must-match": "", + "permission-error": "" + }, + "change-email": { + "email-label": "", + "current-password-label": "", + "email-not-confirmed": "", + "email-updated-title": "", + "email-updated-description": "", + "setup-user-account": "", + "invite-url-label": "", + "invite-url-tooltip": "", + "permission-error": "", + "required-field": "", + "reset": "", + "edit": "", + "cancel": "", + "save": "" + }, + "change-age-restriction": { + "age-restriction-label": "", + "unknowns": "", + "reset": "", + "edit": "", + "cancel": "", + "save": "" + }, + "api-key": { + "copy": "", + "regen-warning": "", + "no-key": "", + "confirm-reset": "", + "key-reset": "" + }, + "scrobbling-providers": { + "title": "", + "requires": "", + "token-expired": "", + "no-token-set": "", + "token-set": "", + "generate": "", + "instructions": "", + "token-input-label": "", + "edit": "", + "cancel": "", + "save": "" + }, + "typeahead": { + "locked-field": "", + "close": "", + "loading": "", + "add-item": "", + "no-data": "", + "add-custom-item": "" + }, + "generic-list-modal": { + "close": "", + "clear": "", + "filter": "", + "open-filtered-search": "" + }, + "user-stats-info-cards": { + "total-pages-read-label": "", + "total-pages-read-tooltip": "", + "total-words-read-label": "", + "total-words-read-tooltip": "", + "time-spent-reading-label": "", + "time-spent-reading-tooltip": "", + "chapters-read-label": "", + "chapters-read-tooltip": "", + "avg-reading-per-week-label": "", + "last-active-label": "", + "chapters": "" + }, + "user-stats": { + "library-read-progress-title": "", + "read-percentage": "" + }, + "top-readers": { + "title": "", + "time-selection-label": "", + "comics-label": "", + "manga-label": "", + "books-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "role-selector": { + "title": "" + }, + "directory-picker": { + "title": "", + "close": "", + "path-label": "", + "path-placeholder": "", + "instructions": "", + "type-header": "", + "name-header": "", + "cancel": "", + "share": "", + "help": "" + }, + "library-access-modal": { + "select-all": "", + "deselect-all": "", + "title": "", + "close": "", + "reset": "", + "cancel": "", + "save": "", + "no-data": "" + }, + "time-periods": { + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "device-platform-pipe": { + "custom": "" + }, + "day-of-week-pipe": { + "monday": "", + "tuesday": "", + "wednesday": "", + "thursday": "", + "friday": "", + "saturday": "", + "sunday": "" + }, + "cbl-import-result-pipe": { + "success": "", + "partial": "", + "failure": "" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "", + "chapter-missing": "", + "empty-file": "", + "name-conflict": "", + "series-collision": "", + "series-missing": "", + "volume-missing": "", + "all-chapter-missing": "", + "invalid-file": "", + "success": "" + }, + "time-duration-pipe": { + "hours": "", + "minutes": "", + "days": "", + "months": "", + "years": "" + }, + "time-ago-pipe": { + "never": "", + "just-now": "", + "min-ago": "", + "mins-ago": "", + "hour-ago": "", + "hours-ago": "", + "day-ago": "", + "days-ago": "", + "month-ago": "", + "months-ago": "", + "year-ago": "", + "years-ago": "" + }, + "relationship-pipe": { + "adaptation": "", + "alternative-setting": "", + "alternative-version": "", + "character": "", + "contains": "", + "doujinshi": "", + "other": "", + "prequel": "", + "sequel": "", + "side-story": "", + "spin-off": "", + "parent": "", + "edition": "" + }, + "publication-status-pipe": { + "ongoing": "", + "hiatus": "", + "completed": "", + "cancelled": "", + "ended": "" + }, + "person-role-pipe": { + "artist": "", + "character": "", + "colorist": "", + "cover-artist": "", + "editor": "", + "inker": "", + "letterer": "", + "penciller": "", + "publisher": "", + "writer": "", + "other": "" + }, + "manga-format-pipe": { + "epub": "", + "archive": "", + "image": "", + "pdf": "", + "unknown": "" + }, + "library-type-pipe": { + "book": "", + "comic": "", + "manga": "" + }, + "age-rating-pipe": { + "unknown": "", + "early-childhood": "", + "adults-only": "", + "everyone": "", + "everyone-10-plus": "", + "g": "", + "kids-to-adults": "", + "mature": "", + "ma15-plus": "", + "mature-17-plus": "", + "rating-pending": "", + "teen": "", + "x18-plus": "", + "not-applicable": "", + "pg": "", + "r18-plus": "" + }, + "reset-password": { + "title": "", + "description": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "submit": "" + }, + "reset-password-modal": { + "title": "", + "new-password-label": "", + "error-label": "", + "close": "", + "cancel": "", + "save": "" + }, + "all-series": { + "series-count": "" + }, + "announcements": { + "title": "" + }, + "changelog": { + "installed": "", + "download": "", + "published-label": "", + "available": "", + "description": "", + "description-continued": "" + }, + "invite-user": { + "title": "", + "close": "", + "description": "", + "email": "", + "required-field": "", + "setup-user-title": "", + "setup-user-description": "", + "setup-user-account": "", + "setup-user-account-tooltip": "", + "invite-url-label": "", + "invite": "", + "inviting": "", + "cancel": "" + }, + "library-selector": { + "title": "", + "select-all": "", + "deselect-all": "", + "no-data": "" + }, + "license": { + "title": "", + "manage": "", + "invalid-license-tooltip": "", + "check": "", + "cancel": "", + "edit": "", + "buy": "", + "activate": "", + "renew": "", + "no-license-key": "", + "license-valid": "", + "license-not-valid": "", + "loading": "", + "activate-description": "", + "activate-license-label": "", + "activate-email-label": "", + "activate-delete": "", + "activate-save": "" + }, + "book-line-overlay": { + "copy": "", + "bookmark": "", + "close": "", + "required-field": "", + "bookmark-label": "", + "save": "" + }, + "book-reader": { + "title": "", + "page-label": "", + "pagination-header": "", + "go-to-page": "", + "go-to-last-page": "", + "prev-page": "", + "next-page": "", + "prev-chapter": "", + "next-chapter": "", + "skip-header": "", + "virtual-pages": "", + "settings-header": "", + "table-of-contents-header": "", + "bookmarks-header": "", + "toc-header": "", + "loading-book": "", + "go-back": "", + "incognito-mode-alt": "", + "incognito-mode-label": "", + "next": "", + "previous": "", + "go-to-page-prompt": "" + }, + "personal-table-of-contents": { + "no-data": "", + "page": "", + "delete": "" + }, + "confirm-email": { + "title": "", + "description": "", + "error-label": "", + "username-label": "", + "password-label": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "confirm-email-change": { + "title": "", + "non-confirm-description": "", + "confirm-description": "", + "success": "" + }, + "confirm-reset-password": { + "title": "", + "description": "", + "password-label": "", + "required-field": "", + "submit": "", + "password-validation": "" + }, + "register": { + "title": "", + "description": "", + "username-label": "", + "email-label": "", + "email-tooltip": "", + "password-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "series-detail": { + "page-settings-title": "", + "close": "", + "layout-mode-label": "", + "layout-mode-option-card": "", + "layout-mode-option-list": "", + "continue-from": "", + "read": "", + "continue": "", + "read-options-alt": "", + "incognito": "", + "remove-from-want-to-read": "", + "add-to-want-to-read": "", + "edit-series-alt": "", + "download-series--tooltip": "", + "downloading-status": "", + "user-reviews-alt": "", + "storyline-tab": "", + "books-tab": "", + "volumes-tab": "", + "specials-tab": "", + "related-tab": "", + "recommendations-tab": "", + "send-to": "", + "no-pages": "", + "no-chapters": "", + "cover-change": "" + }, + "series-metadata-detail": { + "links-title": "", + "genres-title": "", + "tags-title": "", + "collections-title": "", + "reading-lists-title": "", + "writers-title": "", + "cover-artists-title": "", + "characters-title": "", + "colorists-title": "", + "editors-title": "", + "inkers-title": "", + "letterers-title": "", + "translators-title": "", + "pencillers-title": "", + "publishers-title": "", + "promoted": "", + "see-more": "", + "see-less": "" + }, + "badge-expander": { + "more-items": "" + }, + "read-more": { + "read-more": "", + "read-less": "" + }, + "update-notification-modal": { + "title": "", + "close": "", + "help": "", + "download": "" + }, + "side-nav-companion-bar": { + "page-settings-title": "", + "open-filter-and-sort": "", + "close-filter-and-sort": "", + "filter-and-sort-alt": "" + }, + "side-nav": { + "home": "", + "want-to-read": "", + "collections": "", + "reading-lists": "", + "bookmarks": "", + "filter-label": "", + "all-series": "", + "clear": "", + "donate": "" + }, + "library-settings-modal": { + "close": "", + "edit-title": "", + "add-title": "", + "general-tab": "", + "folder-tab": "", + "cover-tab": "", + "advanced-tab": "", + "name-label": "", + "library-name-unique": "", + "last-scanned-label": "", + "type-label": "", + "type-tooltip": "", + "folder-description": "", + "browse": "", + "help-us-part-1": "", + "help-us-part-2": "", + "help-us-part-3": "", + "naming-conventions-part-1": "", + "naming-conventions-part-2": "", + "naming-conventions-part-3": "", + "cover-description": "", + "cover-description-extra": "", + "manage-collection-label": "", + "manage-collection-tooltip": "", + "manage-reading-list-label": "", + "manage-reading-list-tooltip": "", + "allow-scrobbling-label": "", + "allow-scrobbling-tooltip": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "include-in-dashboard-label": "", + "include-in-dashboard-tooltip": "", + "include-in-recommendation-label": "", + "include-in-recommendation-tooltip": "", + "include-in-search-label": "", + "include-in-search-tooltip": "", + "force-scan": "", + "force-scan-tooltip": "", + "reset": "", + "cancel": "", + "next": "", + "save": "", + "required-field": "" + }, + "reader-settings": { + "general-settings-title": "", + "font-family-label": "", + "font-size-label": "", + "line-spacing-label": "", + "margin-label": "", + "reset-to-defaults": "", + "reader-settings-title": "", + "reading-direction-label": "", + "right-to-left": "", + "left-to-right": "", + "horizontal": "", + "vertical": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "on": "", + "off": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "fullscreen-label": "", + "fullscreen-tooltip": "", + "exit": "", + "enter": "", + "layout-mode-label": "", + "layout-mode-tooltip": "", + "layout-mode-option-scroll": "", + "layout-mode-option-1col": "", + "layout-mode-option-2col": "", + "color-theme-title": "", + "theme-dark": "", + "theme-black": "", + "theme-white": "", + "theme-paper": "" + }, + "table-of-contents": { + "no-data": "" + }, + "bookmarks": { + "title": "", + "series-count": "", + "no-data": "", + "no-data-2": "", + "confirm-delete": "", + "confirm-single-delete": "", + "delete-success": "", + "delete-single-success": "" + }, + "bulk-operations": { + "title": "", + "items-selected": "", + "mark-as-unread": "", + "mark-as-read": "", + "deselect-all": "" + }, + "card-detail-drawer": { + "general-tab": "", + "metadata-tab": "", + "cover-tab": "", + "info-tab": "", + "no-summary": "", + "writers-title": "", + "genres-title": "", + "publishers-title": "", + "tags-title": "", + "not-defined": "", + "read": "", + "unread": "", + "files": "", + "pages": "", + "added": "", + "size": "" + }, + "card-detail-layout": { + "total-items": "" + }, + "card-item": { + "cannot-read": "" + }, + "chapter-metadata-detail": { + "no-data": "", + "writers-title": "", + "publishers-title": "", + "characters-title": "", + "translators-title": "", + "letterers-title": "", + "colorists-title": "", + "inkers-title": "", + "pencillers-title": "", + "cover-artists-title": "", + "editors-title": "" + }, + "cover-image-chooser": { + "drag-n-drop": "", + "upload": "", + "upload-continued": "", + "url-label": "", + "load": "", + "back": "", + "reset-cover-tooltip": "", + "reset": "", + "image-num": "", + "apply": "", + "applied": "" + }, + "download-indicator": { + "progress": "" + }, + "edit-series-relation": { + "description-part-1": "", + "description-part-2": "", + "target-series": "", + "relationship": "", + "remove": "", + "add-relationship": "", + "parent": "" + }, + "entity-info-cards": { + "tags-title": "", + "characters-title": "", + "release-date-title": "", + "release-date-tooltip": "", + "age-rating-title": "", + "length-title": "", + "pages-count": "", + "words-count": "", + "reading-time-title": "", + "date-added-title": "", + "size-title": "", + "id-title": "", + "links-title": "", + "isbn-title": "", + "last-read-title": "", + "less-than-hour": "", + "range-hours": "", + "hour": "", + "hours": "", + "read-time-title": "" + }, + "series-info-cards": { + "release-date-title": "", + "release-year-tooltip": "", + "age-rating-title": "", + "language-title": "", + "publication-status-title": "", + "publication-status-tooltip": "", + "scrobbling-title": "", + "scrobbling-tooltip": "", + "on": "", + "off": "", + "disabled": "", + "format-title": "", + "last-read-title": "", + "length-title": "", + "read-time-title": "", + "less-than-hour": "", + "hour": "", + "hours": "", + "time-left-title": "", + "ongoing": "", + "pages-count": "", + "words-count": "" + }, + "bulk-add-to-collection": { + "title": "", + "promoted": "", + "close": "", + "filter-label": "", + "clear": "", + "no-data": "", + "loading": "", + "collection-label": "", + "create": "" + }, + "entity-title": { + "special": "", + "issue-num": "", + "chapter": "" + }, + "external-series-card": { + "open-external": "" + }, + "list-item": { + "read": "" + }, + "manage-alerts": { + "description-part-1": "", + "description-part-2": "", + "filter-label": "", + "clear-alerts": "", + "extension-header": "", + "file-header": "", + "comment-header": "", + "details-header": "" + }, + "manage-email-settings": { + "title": "", + "description": "", + "send-to-warning": "", + "email-url-label": "", + "email-url-tooltip": "", + "reset": "", + "test": "", + "host-name-label": "", + "host-name-tooltip": "", + "host-name-validation": "", + "reset-to-default": "", + "save": "" + }, + "manage-library": { + "title": "", + "add-library": "", + "no-data": "", + "loading": "", + "last-scanned-title": "", + "shared-folders-title": "", + "type-title": "", + "scan-library": "", + "delete-library": "", + "delete-library-by-name": "", + "edit-library": "", + "edit-library-by-name": "" + }, + "manage-media-settings": { + "encode-as-description-part-1": "", + "encode-as-description-part-2": "", + "encode-as-description-part-3": "", + "encode-as-warning": "", + "media-warning": "", + "encode-as-label": "", + "encode-as-tooltip": "", + "bookmark-dir-label": "", + "bookmark-dir-tooltip": "", + "change": "", + "reset-to-default": "", + "reset": "", + "save": "", + "media-issue-title": "", + "scrobble-issue-title": "" + }, + "manage-scrobble-errors": { + "description": "", + "filter-label": "", + "clear-errors": "", + "series-header": "", + "created-header": "", + "comment-header": "", + "edit-header": "", + "edit-item-alt": "" + }, + "default-date-pipe": { + "never": "" + }, + "manage-settings": { + "notice": "", + "restart-required": "", + "base-url-label": "", + "base-url-tooltip": "", + "ip-address-label": "", + "ip-address-tooltip": "", + "port-label": "", + "port-tooltip": "", + "backup-label": "", + "backup-tooltip": "", + "log-label": "", + "log-tooltip": "", + "logging-level-label": "", + "logging-level-tooltip": "", + "cache-size-label": "", + "cache-size-tooltip": "", + "on-deck-last-progress-label": "", + "on-deck-last-progress-tooltip": "", + "on-deck-last-chapter-add-label": "", + "on-deck-last-chapter-add-tooltip": "", + "allow-stats-label": "", + "allow-stats-tooltip-part-1": "", + "allow-stats-tooltip-part-2": "", + "send-data": "", + "opds-label": "", + "opds-tooltip": "", + "enable-opds": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "enable-folder-watching": "", + "reset-to-default": "", + "reset": "", + "save": "", + "cache-size-validation": "", + "field-required": "", + "max-logs-validation": "", + "min-logs-validation": "", + "min-days-validation": "", + "min-cache-validation": "", + "max-backup-validation": "", + "min-backup-validation": "", + "ip-address-validation": "", + "base-url-validation": "" + }, + "manage-system": { + "title": "", + "version-title": "", + "installId-title": "", + "more-info-title": "", + "home-page-title": "", + "wiki-title": "", + "discord-title": "", + "donations-title": "", + "source-title": "", + "feature-request-title": "" + }, + "manage-tasks-settings": { + "title": "", + "library-scan-label": "", + "library-scan-tooltip": "", + "library-database-backup-label": "", + "library-database-backup-tooltip": "", + "adhoc-tasks-title": "", + "job-title-header": "", + "description-header": "", + "action-header": "", + "reset-to-default": "", + "reset": "", + "save": "", + "recurring-tasks-title": "", + "last-executed-header": "", + "cron-header": "", + "convert-media-task": "", + "convert-media-task-desc": "", + "convert-media-success": "", + "bust-cache-task": "", + "bust-cache-task-desc": "", + "bust-cache-task-success": "", + "clear-reading-cache-task": "", + "clear-reading-cache-task-desc": "", + "clear-reading-cache-task-success": "", + "clean-up-want-to-read-task": "", + "clean-up-want-to-read-task-desc": "", + "clean-up-want-to-read-task-success": "", + "backup-database-task": "", + "backup-database-task-desc": "", + "backup-database-task-success": "", + "download-logs-task": "", + "download-logs-task-desc": "", + "analyze-files-task": "", + "analyze-files-task-desc": "", + "analyze-files-task-success": "", + "check-for-updates-task": "", + "check-for-updates-task-desc": "" + }, + "manage-users": { + "title": "", + "invite": "", + "you-alt": "", + "pending-title": "", + "delete-user-tooltip": "", + "delete-user-alt": "", + "edit-user-tooltip": "", + "edit-user-alt": "", + "resend-invite-tooltip": "", + "resend-invite-alt": "", + "setup-user-tooltip": "", + "setup-user-alt": "", + "change-password-tooltip": "", + "change-password-alt": "", + "resend": "", + "setup": "", + "last-active-title": "", + "roles-title": "", + "none": "", + "never": "", + "online-now-tooltip": "", + "sharing-title": "", + "no-data": "", + "loading": "" + }, + "edit-collection-tags": { + "title": "", + "required-field": "", + "save": "", + "close": "", + "cancel": "", + "general-tab": "", + "cover-image-tab": "", + "series-tab": "", + "name-label": "", + "name-validation": "", + "promote-label": "", + "promote-tooltip": "", + "summary-label": "", + "series-title": "", + "deselect-all": "", + "select-all": "" + }, + "library-detail": { + "library-tab": "", + "recommended-tab": "" + }, + "library-recommended": { + "no-data": "", + "more-in-genre": "", + "rediscover": "", + "highly-rated": "", + "quick-catchups": "", + "quick-reads": "", + "on-deck": "" + }, + "admin-dashboard": { + "title": "", + "general-tab": "", + "users-tab": "", + "libraries-tab": "", + "media-tab": "", + "logs-tab": "", + "email-tab": "", + "tasks-tab": "", + "statistics-tab": "", + "system-tab": "", + "kavita+-tab": "", + "kavita+-desc-part-1": "", + "kavita+-desc-part-2": "", + "kavita+-desc-part-3": "" + }, + "collection-detail": { + "no-data": "", + "no-data-filtered": "", + "title-alt": "" + }, + "all-collections": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "carousel-reel": { + "prev-items": "", + "next-items": "" + }, + "draggable-ordered-list": { + "instructions-alt": "", + "reorder-label": "", + "remove-item-alt": "" + }, + "reading-lists": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "reading-list-item": { + "remove": "", + "read": "" + }, + "reading-list-detail": { + "item-count": "", + "page-settings-title": "", + "remove-read": "", + "order-numbers-label": "", + "continue": "", + "read": "", + "read-options-alt": "", + "incognito-alt": "", + "no-data": "" + }, + "events-widget": { + "title-alt": "", + "dismiss-all": "", + "update-available": "", + "downloading-item": "", + "more-info": "", + "close": "", + "users-online-count": "", + "active-events-title": "", + "no-data": "" + }, + "shortcuts-modal": { + "title": "", + "close": "", + "prev-page": "", + "next-page": "", + "go-to": "", + "bookmark": "", + "double-click": "", + "close-reader": "", + "toggle-menu": "" + }, + "grouped-typeahead": { + "files": "", + "chapters": "", + "people": "", + "tags": "", + "genres": "", + "libraries": "", + "reading-lists": "", + "collections": "", + "close": "", + "loading": "" + }, + "nav-header": { + "skip-alt": "", + "search-series-alt": "", + "search-alt": "", + "promoted": "", + "no-data": "", + "scroll-to-top-alt": "", + "server-settings": "", + "settings": "", + "help": "", + "announcements": "", + "logout": "" + }, + "add-to-list-modal": { + "title": "", + "close": "", + "filter-label": "", + "promoted-alt": "", + "no-data": "", + "loading": "", + "reading-list-label": "", + "create": "" + }, + "edit-reading-list-modal": { + "title": "", + "general-tab": "", + "cover-image-tab": "", + "close": "", + "save": "", + "year-validation": "", + "month-validation": "", + "name-unique-validation": "", + "required-field": "", + "summary-label": "", + "year-label": "", + "month-label": "", + "ending-title": "", + "starting-title": "", + "promote-label": "", + "promote-tooltip": "" + }, + "import-cbl-modal": { + "close": "", + "title": "", + "import-description": "", + "validate-description": "", + "validate-warning": "", + "validate-no-issue": "", + "validate-no-issue-description": "", + "dry-run-description": "", + "prev": "", + "import": "", + "restart": "", + "next": "", + "import-step": "", + "validate-cbl-step": "", + "dry-run-step": "", + "final-import-step": "" + }, + "pdf-reader": { + "loading-message": "", + "incognito-mode": "", + "light-theme-alt": "", + "dark-theme-alt": "", + "close-reader-alt": "" + }, + "manga-reader": { + "back": "", + "save-globally": "", + "incognito-alt": "", + "incognito-title": "", + "shortcuts-menu-alt": "", + "prev-page-tooltip": "", + "next-page-tooltip": "", + "prev-chapter-tooltip": "", + "next-chapter-tooltip": "", + "first-page-tooltip": "", + "last-page-tooltip": "", + "left-to-right-alt": "", + "right-to-left-alt": "", + "reading-direction-tooltip": "", + "reading-mode-tooltip": "", + "collapse": "", + "fullscreen": "", + "settings-tooltip": "", + "image-splitting-label": "", + "image-scaling-label": "", + "height": "", + "width": "", + "original": "", + "auto-close-menu-label": "", + "swipe-enabled-label": "", + "enable-comic-book-label": "", + "brightness-label": "", + "first-time-reading-manga": "", + "layout-mode-switched": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "user-preferences-updated": "", + "emulate-comic-book-label": "" + }, + "metadata-filter": { + "filter-title": "", + "format-label": "", + "libraries-label": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "read-progress-label": "", + "unread": "", + "read": "", + "in-progress": "", + "rating-label": "", + "age-rating-label": "", + "language-label": "", + "publication-status-label": "", + "series-name-label": "", + "series-name-tooltip": "", + "release-label": "", + "min": "", + "max": "", + "sort-by-label": "", + "ascending-alt": "", + "descending-alt": "", + "reset": "", + "apply": "" + }, + "sort-field-pipe": { + "sort-name": "", + "created": "", + "last-modified": "", + "last-chapter-added": "", + "time-to-read": "", + "release-year": "" + }, + "edit-series-modal": { + "title": "", + "general-tab": "", + "metadata-tab": "", + "people-tab": "", + "web-links-tab": "", + "cover-image-tab": "", + "related-tab": "", + "info-tab": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "language-label": "", + "age-rating-label": "", + "publication-status-label": "", + "required-field": "", + "close": "", + "name-label": "", + "sort-name-label": "", + "localized-name-label": "", + "summary-label": "", + "release-year-label": "", + "web-link-description": "", + "web-link-label": "", + "add-link-alt": "", + "remove-link-alt": "", + "cover-image-description": "", + "save": "", + "field-locked-alt": "", + "info-title": "", + "library-title": "", + "format-title": "", + "created-title": "", + "last-read-title": "", + "last-added-title": "", + "last-scanned-title": "", + "folder-path-title": "", + "publication-status-title": "", + "total-pages-title": "", + "total-items-title": "", + "max-items-title": "", + "size-title": "", + "loading": "", + "added-title": "", + "last-modified-title": "", + "view-files": "", + "pages-title": "", + "chapter-title": "", + "volume-num": "", + "highest-count-tooltip": "", + "max-issue-tooltip": "" + }, + "day-breakdown": { + "title": "", + "x-axis-label": "", + "y-axis-label": "" + }, + "file-breakdown-stats": { + "format-title": "", + "format-tooltip": "", + "visualisation-label": "", + "data-table-label": "", + "extension-header": "", + "format-header": "", + "total-size-header": "", + "total-files-header": "", + "not-classified": "", + "total-file-size-title": "" + }, + "reading-activity": { + "title": "", + "legend-label": "", + "x-axis-label": "", + "y-axis-label": "", + "no-data": "", + "time-frame-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "manga-format-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "format-header": "", + "count-header": "" + }, + "publication-status-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "year-header": "", + "count-header": "" + }, + "server-stats": { + "total-series-label": "", + "total-series-tooltip": "", + "total-volumes-label": "", + "total-volumes-tooltip": "", + "total-files-label": "", + "total-files-tooltip": "", + "total-size-label": "", + "total-genres-label": "", + "total-genres-tooltip": "", + "total-tags-label": "", + "total-tags-tooltip": "", + "total-people-label": "", + "total-people-tooltip": "", + "total-read-time-label": "", + "total-read-time-tooltip": "", + "series": "", + "reads": "", + "release-years-title": "", + "most-active-users-title": "", + "popular-libraries-title": "", + "popular-series-title": "", + "recently-read-title": "", + "genre-count": "", + "tag-count": "", + "people-count": "", + "tags": "", + "people": "", + "genres": "" + }, + "errors": { + "series-doesnt-exist": "", + "collection-invalid-access": "", + "unknown-crit": "", + "user-not-auth": "", + "error-code": "", + "download": "", + "not-found": "", + "generic": "", + "rejected-cover-upload": "", + "invalid-confirmation-url": "", + "invalid-confirmation-email": "", + "invalid-password-reset-url": "" + }, + "toasts": { + "regen-cover": "", + "no-pages": "", + "download-in-progress": "", + "scan-queued": "", + "server-settings-updated": "", + "reset-ip-address": "", + "reset-base-url": "", + "unauthorized-1": "", + "unauthorized-2": "", + "no-updates": "", + "confirm-delete-user": "", + "user-deleted": "", + "email-sent-to-user": "", + "click-email-link": "", + "series-added-to-collection": "", + "no-series-collection-warning": "", + "collection-updated": "", + "reading-list-deleted": "", + "reading-list-updated": "", + "confirm-delete-reading-list": "", + "item-removed": "", + "nothing-to-remove": "", + "series-added-to-reading-list": "", + "volumes-added-to-reading-list": "", + "chapter-added-to-reading-list": "", + "multiple-added-to-reading-list": "", + "select-files-warning": "", + "reading-list-imported": "", + "incognito-off": "", + "email-service-reset": "", + "email-service-reachable": "", + "email-service-unresponsive": "", + "refresh-covers-queued": "", + "library-file-analysis-queued": "", + "entity-read": "", + "entity-unread": "", + "mark-read": "", + "mark-unread": "", + "series-removed-want-to-read": "", + "series-deleted": "", + "file-send-to": "", + "theme-missing": "", + "email-sent": "", + "k+-license-saved": "", + "k+-unlocked": "", + "k+-error": "", + "k+-delete-key": "", + "library-deleted": "", + "copied-to-clipboard": "", + "book-settings-info": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "load-next-chapter": "", + "load-prev-chapter": "", + "account-registration-complete": "", + "account-migration-complete": "", + "password-reset": "", + "password-updated": "", + "forced-scan-queued": "", + "library-created": "", + "anilist-token-updated": "", + "age-restriction-updated": "", + "email-sent-to-no-existing": "", + "email-sent-to": "", + "change-email-private": "", + "device-updated": "", + "device-created": "", + "confirm-regen-covers": "", + "alert-long-running": "", + "confirm-delete-multiple-series": "", + "confirm-delete-series": "", + "alert-bad-theme": "", + "confirm-library-delete": "", + "confirm-library-type-change": "", + "confirm-download-size": "" + }, + "actionable": { + "scan-library": "", + "refresh-covers": "", + "analyze-files": "", + "settings": "", + "edit": "", + "mark-as-read": "", + "mark-as-unread": "", + "scan-series": "", + "add-to": "", + "add-to-want-to-read": "", + "remove-from-want-to-read": "", + "remove-from-on-deck": "", + "others": "", + "add-to-reading-list": "", + "add-to-collection": "", + "send-to": "", + "delete": "", + "download": "", + "read-incognito": "", + "details": "", + "view-series": "", + "clear": "", + "import-cbl": "", + "read": "" + }, + "preferences": { + "left-to-right": "", + "right-to-left": "", + "horizontal": "", + "vertical": "", + "automatic": "", + "fit-to-height": "", + "fit-to-width": "", + "original": "", + "fit-to-screen": "", + "no-split": "", + "webtoon": "", + "single": "", + "double": "", + "double-manga": "", + "scroll": "", + "1-column": "", + "2-column": "", + "cards": "", + "list": "", + "up-to-down": "" + }, + "validation": { + "required-field": "", + "valid-email": "", + "password-validation": "" + }, + "entity-type": { + "volume": "", + "chapter": "", + "series": "", + "bookmark": "", + "logs": "" + }, + "common": { + "reset-to-default": "", + "close": "", + "cancel": "", + "create": "", + "save": "", + "reset": "", + "add": "", + "apply": "", + "delete": "", + "edit": "", + "help": "", + "submit": "", + "email": "", + "read": "", + "loading": "", + "username": "", + "password": "", + "promoted": "", + "select-all": "", + "deselect-all": "", + "series-count": "", + "item-count": "", + "book-num": "", + "issue-hash-num": "", + "issue-num": "", + "chapter-num": "", + "volume-num": "" + } +} diff --git a/UI/Web/src/assets/langs/it.json b/UI/Web/src/assets/langs/it.json index 72e27ff3a9..791a449b5f 100644 --- a/UI/Web/src/assets/langs/it.json +++ b/UI/Web/src/assets/langs/it.json @@ -87,12 +87,12 @@ "theme-tab": "Tema", "devices-tab": "Dispositivi", "stats-tab": "Stato", - "scrobbling-tab": "", + "scrobbling-tab": "Scrubbling", "success-toast": "Preferenze utente aggiornate", "global-settings-title": "Impostazioni globali", "page-layout-mode-label": "Modalità layout di pagina", "page-layout-mode-tooltip": "Mostra gli elementi come schede o visualizzazione elenco nella pagina Dettaglio serie.", - "locale-label": "Locale", + "locale-label": "Localizzazione", "locale-tooltip": "La lingua che Kavita dovrebbe usare", "blur-unread-summaries-label": "Sfoca i riepiloghi non letti", "blur-unread-summaries-tooltip": "Sfoca il testo di riepilogo su volumi o capitoli che non hanno avanzamento di lettura (per evitare spoiler)", @@ -116,125 +116,125 @@ "layout-mode-tooltip": "Renderizza una singola immagine sullo schermo o due immagini affiancate", "background-color-label": "Colore di sfondo", "auto-close-menu-label": "Menu di chiusura automatica", - "show-screen-hints-label": "", - "emulate-comic-book-label": "", - "swipe-to-paginate-label": "", - "book-reader-settings-title": "", - "tap-to-paginate-label": "", - "tap-to-paginate-tooltip": "", - "immersive-mode-label": "", - "immersive-mode-tooltip": "", - "reading-direction-book-label": "", - "reading-direction-book-tooltip": "", - "font-family-label": "", - "font-family-tooltip": "", - "writing-style-label": "", - "writing-style-tooltip": "", - "layout-mode-book-label": "", - "layout-mode-book-tooltip": "", - "color-theme-book-label": "", - "color-theme-book-tooltip": "", - "font-size-book-label": "", - "line-height-book-label": "", - "line-height-book-tooltip": "", - "margin-book-label": "", - "margin-book-tooltip": "", - "clients-opds-alert": "", - "clients-opds-description": "", - "clients-api-key-tooltip": "", - "clients-opds-url-tooltip": "", - "reset": "", - "save": "" + "show-screen-hints-label": "Mostra suggerimenti sullo schermo", + "emulate-comic-book-label": "Emula fumetto", + "swipe-to-paginate-label": "Scorri per impaginare", + "book-reader-settings-title": "Lettore di Libri", + "tap-to-paginate-label": "Tocca per impaginare", + "tap-to-paginate-tooltip": "Se i lati dello schermo del lettore di libri consentono di toccarlo per passare alla pagina precedente/successiva", + "immersive-mode-label": "Modalità Immersiva", + "immersive-mode-tooltip": "Questo nasconderà il menu. Un clic sulla pagina del lettore e si attiverà il \"tocca per impaginare\"", + "reading-direction-book-label": "Direzione di lettura", + "reading-direction-book-tooltip": "Direzione da cliccare per passare alla pagina successiva. Da destra a sinistra significa che fai clic sul lato sinistro dello schermo per passare alla pagina successiva.", + "font-family-label": "Tipologia di Font", + "font-family-tooltip": "Tipologia di caratteri da caricare. Predefinito caricherà il carattere predefinito del libro", + "writing-style-label": "Stile di Scrittura", + "writing-style-tooltip": "Cambia la direzione del testo. Orizzontale è da sinistra a destra, verticale è dall'alto verso il basso.", + "layout-mode-book-label": "Modalità di disposizione delle pagine", + "layout-mode-book-tooltip": "Come devono essere strutturati i contenuti. Sfoglia è come lo presenta il libro. 1 o 2 colonne adatta all'altezza del dispositivo ed utilizza 1 o 2 colonne di testo per pagina", + "color-theme-book-label": "Tema a Colori", + "color-theme-book-tooltip": "Quale tema applicare al contenuto e al menu del lettore di libri", + "font-size-book-label": "Dimensioni del carattere", + "line-height-book-label": "Spaziatura interlinea", + "line-height-book-tooltip": "Quanta spaziatura tra le righe del libro", + "margin-book-label": "Margine", + "margin-book-tooltip": "Quanto margine lasciare su ciascun lato dello schermo. Sui dispositivi mobili verrà ignorato il valore, indipendentemente da questo settaggio.", + "clients-opds-alert": "OPDS non è abilitato su questo server. Ciò non influirà sugli utenti Tachiyomi.", + "clients-opds-description": "Tutti i client di terze parti utilizzeranno la chiave API o l'URL di connessione di seguito. Sono come password, tienile private.", + "clients-api-key-tooltip": "La chiave API è come una password. Tienila segreta, tienila al sicuro.", + "clients-opds-url-tooltip": "OPDS URL", + "reset": "{{common.reset}}", + "save": "{{common.save}}" }, "user-holds": { - "title": "", - "description": "" + "title": "Mantieni l'Ascolto", + "description": "Questo è un elenco di serie gestito dall'utente che non verrà sottoposto a scrobbling . Puoi rimuovere una serie in qualsiasi momento e il prossimo evento in grado di attivare lo scrobbling (avanzamento della lettura, valutazione, desidera leggere lo stato) attiverà gli eventi." }, "theme-manager": { - "title": "", - "looking-for-theme": "", - "looking-for-theme-continued": "", - "scan": "", - "site-themes": "", - "set-default": "", - "apply": "", - "applied": "", - "updated-toastr": "", - "scan-queued": "" + "title": "Gestore dei Temi", + "looking-for-theme": "Cerchi un tema leggero o e-ink? Abbiamo alcuni temi personalizzati che puoi utilizzare sul nostro ", + "looking-for-theme-continued": "tema github.", + "scan": "Scansione", + "site-themes": "Temi del sito", + "set-default": "Imposta default", + "apply": "{{common.apply}}", + "applied": "Applicato", + "updated-toastr": "L'impostazione predefinita del sito è stata aggiornata a {{name}}", + "scan-queued": "È stata messa in coda una scansione del tema del sito" }, "theme": { - "theme-dark": "", - "theme-black": "", - "theme-paper": "", - "theme-white": "" + "theme-dark": "Scuro", + "theme-black": "Nero", + "theme-paper": "Carta", + "theme-white": "Bianco" }, "restriction-selector": { - "title": "", - "description": "", - "not-applicable-for-admins": "", - "age-rating-label": "", - "no-restriction": "", - "include-unknowns-label": "", - "include-unknowns-tooltip": "" + "title": "Limitazione della classificazione in base all'età", + "description": "Quando selezionata, tutte le serie e gli elenchi di lettura che hanno almeno un elemento che viola la restrizione selezionata verranno omessi dai risultati.", + "not-applicable-for-admins": "Non applicabile per gli amministratori.", + "age-rating-label": "Classificazione per Età", + "no-restriction": "Nessuna Restrizione", + "include-unknowns-label": "Includi sconosciuti", + "include-unknowns-tooltip": "Se vero, Sconosciuti verranno consentiti con le limitazioni di età. Ciò potrebbe portare a perdite di contenuti multimediali senza tag per gli utenti con limiti di età." }, "site-theme-provider-pipe": { - "system": "", - "user": "" + "system": "Sistema", + "user": "Utente" }, "manage-devices": { - "title": "", - "description": "", - "devices-title": "", - "no-devices": "", - "platform-label": "", - "email-label": "", - "add": "", - "delete": "", - "edit": "" + "title": "Gestore Devices", + "description": "Questa sezione consente di configurare i dispositivi che non possono connettersi a Kavita tramite un browser Web e dispongono invece di un indirizzo e-mail che accetta i file.", + "devices-title": "Dispositivi", + "no-devices": "Non ci sono ancora dispositivi configurati", + "platform-label": "Piattaforma: ", + "email-label": "Email: ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" }, "edit-device": { - "device-name-label": "", - "email-label": "", - "email-tooltip": "", - "device-platform-label": "", - "save": "", - "required-field": "", - "valid-email": "" + "device-name-label": "Nome Dispositivo", + "email-label": "{{common.email}}", + "email-tooltip": "Questa e-mail verrà utilizzata per recapitare il file tramite Invia a", + "device-platform-label": "Piattaforma del dispositivo", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" }, "change-password": { - "password-label": "", - "current-password-label": "", - "new-password-label": "", - "confirm-password-label": "", - "reset": "", - "edit": "", - "cancel": "", - "save": "", - "required-field": "", - "passwords-must-match": "", - "permission-error": "" + "password-label": "{{common.password}}", + "current-password-label": "Password Corrente", + "new-password-label": "Nuova Password", + "confirm-password-label": "Conferma Password", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "Le Password devono essere identiche", + "permission-error": "Non sei autorizzato a modificare la tua password. Contatta l'amministratore del server." }, "change-email": { - "email-label": "", - "current-password-label": "", - "email-not-confirmed": "", - "email-updated-title": "", - "email-updated-description": "", - "setup-user-account": "", - "invite-url-label": "", - "invite-url-tooltip": "", - "permission-error": "", - "required-field": "", - "reset": "", - "edit": "", - "cancel": "", - "save": "" + "email-label": "{{common.email}}", + "current-password-label": "Password Corrente", + "email-not-confirmed": "Questa email non è stata confermata", + "email-updated-title": "Email Aggiornata", + "email-updated-description": "Puoi utilizzare il seguente link qui sotto per confermare l'e-mail del tuo account. Se il tuo server è accessibile dall'esterno, sarà stata inviata un'e-mail contenente un collegamento che potrà essere utilizzato per confermare l'e-mail.", + "setup-user-account": "Configura l'account utente", + "invite-url-label": "URL di invito", + "invite-url-tooltip": "Copia questo e incollalo in una nuova scheda", + "permission-error": "Non sei autorizzato a modificare la tua email. Contatta l'amministratore del server.", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "change-age-restriction": { - "age-restriction-label": "Restrizione età", + "age-restriction-label": "Restrizione per età", "unknowns": "Sconosciuti", - "reset": "", - "edit": "", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", "save": "{{common.save}}" }, @@ -243,7 +243,8 @@ "regen-warning": "La rigenerazione della chiave API invaliderà tutti i client esistenti.", "no-key": "ERRORE - CHIAVE NON IMPOSTATA", "confirm-reset": "Ciò invaliderà tutte le configurazioni OPDS impostate. Sei sicuro di voler continuare?", - "key-reset": "Ripristino della chiave API" + "key-reset": "Ripristino della chiave API", + "show": "Mostra" }, "scrobbling-providers": { "title": "Fornitori di Scrobbling", @@ -251,7 +252,7 @@ "token-expired": "Token scaduto", "no-token-set": "Nessun Token impostato", "token-set": "Token Impostato", - "generate": "Generate", + "generate": "Genera", "instructions": "Gli utenti per la prima volta devono fare clic su \"{{scrobbling-providers.generate}}\" di seguito per consentire a Kavita+ di parlare con {{service}}. Una volta autorizzato il programma, copia e incolla il token nell'input sottostante. Puoi rigenerare il tuo token in qualsiasi momento.", "token-input-label": "Il token {{service}} va qui", "edit": "{{common.edit}}", @@ -263,13 +264,13 @@ "close": "{{common.close}}", "loading": "{{common.loading}}", "add-item": "Aggiungi {{item}}…", - "no-data": "Nessun dato", + "no-data": "Nessun dato presente", "add-custom-item": ", digitare per aggiungere un elemento personalizzato" }, "generic-list-modal": { "close": "{{common.close}}", - "clear": "Pulito", - "filter": "Filtro", + "clear": "Pulisci", + "filter": "Filtra", "open-filtered-search": "Apri una ricerca filtrata per {{item}}" }, "user-stats-info-cards": { @@ -290,59 +291,59 @@ "read-percentage": "% Letto" }, "top-readers": { - "title": "", - "time-selection-label": "", - "comics-label": "", - "manga-label": "", - "books-label": "", - "this-week": "", - "last-7-days": "", - "last-30-days": "", - "last-90-days": "", - "last-year": "", - "all-time": "" + "title": "I migliori lettori", + "time-selection-label": "Lasso di tempo", + "comics-label": "Fumetti: {{value}} ore", + "manga-label": "Manga: {{value}} ore", + "books-label": "Libri: {{value}} ore", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" }, "role-selector": { - "title": "" + "title": "Regole" }, "directory-picker": { - "title": "", - "close": "", - "path-label": "", - "path-placeholder": "", - "instructions": "", - "type-header": "", - "name-header": "", - "cancel": "", - "share": "", - "help": "" + "title": "Scegli un Dizionario", + "close": "{{common.close}}", + "path-label": "Percorso", + "path-placeholder": "Inizia a digitare o seleziona il percorso", + "instructions": "Seleziona una cartella per visualizzare il breadcrumb. Non vedi la tua directory? Prova prima a controllare /.", + "type-header": "Tipo", + "name-header": "Nome", + "cancel": "{{common.cancel}}", + "share": "Condivisi", + "help": "{{common.help}}" }, "library-access-modal": { - "select-all": "", - "deselect-all": "", - "title": "", - "close": "", - "reset": "", - "cancel": "", - "save": "", - "no-data": "" + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "Accesso Libreria", + "close": "{{common.close}}", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "no-data": "Non ci sono ancora librerie configurate." }, "time-periods": { - "this-week": "", - "last-7-days": "", - "last-30-days": "", - "last-90-days": "", - "last-year": "", - "all-time": "" + "this-week": "Questa settimana", + "last-7-days": "Ultimi 7 giorni", + "last-30-days": "Ultimi 30 giorni", + "last-90-days": "Ultimi 90 giorni", + "last-year": "Ultimo Anno", + "all-time": "Sempre" }, "device-platform-pipe": { - "custom": "" + "custom": "Personalizzato" }, "day-of-week-pipe": { - "monday": "", - "tuesday": "", - "wednesday": "", - "thursday": "", + "monday": "Lunedì", + "tuesday": "Martedì", + "wednesday": "Mercoledì", + "thursday": "Giovedì", "friday": "Venerdì", "saturday": "Sabato", "sunday": "Domenia" @@ -374,1315 +375,1384 @@ "time-ago-pipe": { "just-now": "proprio adesso", "min-ago": "un minuto fà", - "mins-ago": "", - "hour-ago": "", - "hours-ago": "", - "day-ago": "", - "days-ago": "", - "month-ago": "", - "months-ago": "", - "year-ago": "", - "years-ago": "", + "mins-ago": "{{value}} minuti fà", + "hour-ago": "una ora fà", + "hours-ago": "{{value}} ore fà", + "day-ago": "un giorno fà", + "days-ago": "{{value}} giorni fà", + "month-ago": "un mese fà", + "months-ago": "{{value}} mesi fà", + "year-ago": "un anno fà", + "years-ago": "{{value}} anni fà", "never": "Mai" }, "relationship-pipe": { - "adaptation": "", - "alternative-setting": "", - "alternative-version": "", - "character": "", - "contains": "", - "doujinshi": "", - "other": "", - "prequel": "", - "sequel": "", - "side-story": "", - "spin-off": "", - "parent": "", - "edition": "" + "adaptation": "Adattamento", + "alternative-setting": "Impostazioni Alternative", + "alternative-version": "Versione Alternativa", + "character": "Carattere", + "contains": "Contiene", + "doujinshi": "Doujinshi", + "other": "Altro", + "prequel": "Prequel", + "sequel": "Sequel", + "side-story": "Storia parallela", + "spin-off": "Spin Off", + "parent": "Parente", + "edition": "Edizione" }, "publication-status-pipe": { - "ongoing": "", - "hiatus": "", - "completed": "", - "cancelled": "", - "ended": "" + "ongoing": "Incorsa", + "hiatus": "Lato", + "completed": "Completato", + "cancelled": "Cancellato", + "ended": "Finito" }, "person-role-pipe": { - "artist": "", - "character": "", - "colorist": "", - "cover-artist": "", - "editor": "", - "inker": "", - "letterer": "", - "penciller": "", - "publisher": "", - "writer": "", - "other": "" + "artist": "Artista", + "character": "Carattere", + "colorist": "Colorista", + "cover-artist": "Artista della copertina", + "editor": "Editor", + "inker": "Inchiostratore", + "letterer": "Letterario", + "penciller": "Disegnatore", + "publisher": "Pubblicatore", + "writer": "Scrittore", + "other": "Altro" }, "manga-format-pipe": { - "epub": "", - "archive": "", - "image": "", - "pdf": "", - "unknown": "" + "epub": "EPUB", + "archive": "Archivio", + "image": "Immagine", + "pdf": "PDF", + "unknown": "Sconosciuto" }, "library-type-pipe": { - "book": "", - "comic": "", - "manga": "" + "book": "Libro", + "comic": "Fumetto", + "manga": "Manga" }, "age-rating-pipe": { - "unknown": "", - "early-childhood": "", - "adults-only": "", - "everyone": "", - "everyone-10-plus": "", - "g": "", - "kids-to-adults": "", - "mature": "", - "ma15-plus": "", - "mature-17-plus": "", - "rating-pending": "", - "teen": "", - "x18-plus": "", - "not-applicable": "", - "pg": "", - "r18-plus": "" + "unknown": "Sconosciuto", + "early-childhood": "Prima infanzia", + "adults-only": "Solo adulti 18+", + "everyone": "Tutti", + "everyone-10-plus": "Tutti 10+", + "g": "G", + "kids-to-adults": "Bambini ad adulti", + "mature": "Maturo", + "ma15-plus": "MA15+", + "mature-17-plus": "Maturo 17+", + "rating-pending": "Valutazione in sospeso", + "teen": "Adolescente", + "x18-plus": "X18+", + "not-applicable": "Non Applicabile", + "pg": "PG", + "r18-plus": "R18+" }, "reset-password": { - "title": "", - "description": "", - "email-label": "", - "required-field": "", - "valid-email": "", - "submit": "" + "title": "Resetta la password", + "description": "Inserisci l'e-mail del tuo account. Kavita ti invierà un'email se valida in archivio, altrimenti chiedi all'amministratore il link dai log.", + "email-label": "{{common.email}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "submit": "{{common.submit}}" }, "reset-password-modal": { - "title": "", - "new-password-label": "", - "error-label": "", - "close": "", - "cancel": "", - "save": "" + "title": "Reimposta la password di {{nome utente}}", + "new-password-label": "Nuova Password", + "error-label": "Errore: ", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "all-series": { - "series-count": "" + "series-count": "{{common.series-count}}", + "title": "Tutte le Serie" }, "announcements": { - "title": "" + "title": "Annunci" }, "changelog": { - "installed": "", - "download": "", - "published-label": "", - "available": "", - "description": "", - "description-continued": "" + "installed": "Installato", + "download": "Download", + "published-label": "Pubblicato: ", + "available": "Disponibile", + "description": "Se non vedi un {{installed}}", + "description-continued": "tag, sei in una nightly release. Solo le versioni principali verranno visualizzate come disponibili." }, "invite-user": { - "title": "", - "close": "", - "description": "", - "email": "", - "required-field": "", - "setup-user-title": "", - "setup-user-description": "", - "setup-user-account": "", - "setup-user-account-tooltip": "", - "invite-url-label": "", - "invite": "", - "inviting": "", - "cancel": "" + "title": "Invita utente", + "close": "{{common.close}}", + "description": "Invita un utente sul tuo server. Inserisci la loro email e invieremo loro un'email per creare un account. Se non desideri utilizzare il nostro servizio di posta elettronica, puoi ospitare il tuo come un servizio di posta elettronica o utilizzare un'e-mail falsa (Utente dimenticato non funzionerà). Verrà comunque presentato un collegamento che potrà essere utilizzato per configurare manualmente l'account.", + "email": "{{common.email}}", + "required-field": "{{common.required-field}}", + "setup-user-title": "Utente Invitato", + "setup-user-description": "Puoi utilizzare il collegamento seguente per configurare l'account per il tuo utente o utilizzare il pulsante Copia. Potrebbe essere necessario disconnettersi prima di utilizzare il collegamento per registrare un nuovo utente. Se il tuo server è accessibile dall'esterno, verrà inviata un'e-mail all'utente e i collegamenti potranno essere utilizzati da lui per completare la configurazione del proprio account.", + "setup-user-account": "Configura l'account dell'utente", + "setup-user-account-tooltip": "Copialo e incollalo in una nuova scheda. Potrebbe essere necessario disconnettersi.", + "invite-url-label": "URL invito", + "invite": "Invito", + "inviting": "Sto Invitando…", + "cancel": "{{common.cancel}}" }, "library-selector": { - "title": "", - "select-all": "", - "deselect-all": "", - "no-data": "" + "title": "Librerie", + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "no-data": "Non ci sono ancora librerie configurate." }, "license": { - "title": "", - "manage": "", - "invalid-license-tooltip": "", - "check": "", - "cancel": "", - "edit": "", - "buy": "", - "activate": "", - "renew": "", - "no-license-key": "", - "license-valid": "", - "license-not-valid": "", - "loading": "", - "activate-description": "", - "activate-license-label": "", - "activate-email-label": "", - "activate-delete": "", - "activate-save": "" + "title": "Licenza Kavita+", + "manage": "Gestisci", + "invalid-license-tooltip": "Se il tuo abbonamento è scaduto, devi inviare un'e-mail al supporto per creare un nuovo abbonamento", + "check": "Controlla", + "cancel": "{{common.cancel}}", + "edit": "{{common.edit}}", + "buy": "Compra", + "activate": "Attivare", + "renew": "Rinnova", + "no-license-key": "Nessuna chiave di licenza", + "license-valid": "Licenza Valida", + "license-not-valid": "Licenza Non Valida", + "loading": "{{common.loading}}", + "activate-description": "Inserisci il codice di licenza e l'e-mail utilizzati per registrarti con Stripe", + "activate-license-label": "Chiave Licenza", + "activate-email-label": "{{common.email}}", + "activate-delete": "Eliminare", + "activate-save": "{{common.save}}" }, "book-line-overlay": { - "copy": "", - "bookmark": "", - "close": "", - "required-field": "", - "bookmark-label": "", - "save": "" + "copy": "Copia", + "bookmark": "Segnalibro", + "close": "{{common.close}}", + "required-field": "{{common.required-field}}", + "bookmark-label": "Nome Segnalibro", + "save": "{{common.save}}" }, "book-reader": { - "title": "", - "page-label": "", - "pagination-header": "", - "go-to-page": "", - "go-to-last-page": "", - "prev-page": "", - "next-page": "", - "prev-chapter": "", - "next-chapter": "", - "skip-header": "", - "virtual-pages": "", - "settings-header": "", - "table-of-contents-header": "", - "bookmarks-header": "", - "toc-header": "", - "loading-book": "", - "go-back": "", - "incognito-mode-alt": "", - "incognito-mode-label": "", - "next": "", - "previous": "" + "title": "Impostazioni del libro", + "page-label": "Pagina", + "pagination-header": "Sezione", + "go-to-page": "Vai alla pagina", + "go-to-last-page": "Vai all'ultima pagina", + "prev-page": "Pagina prec", + "next-page": "Pagina Suc", + "prev-chapter": "Capitolo/Volume prec", + "next-chapter": "Capitolo/Volume Suc", + "skip-header": "Passa al contenuto principale", + "virtual-pages": "pagine virtuali", + "settings-header": "Impostazioni", + "table-of-contents-header": "Tabella dei contenuti", + "bookmarks-header": "Segnalibri", + "toc-header": "ToC", + "loading-book": "Caricamento libro…", + "go-back": "Torna indietro", + "incognito-mode-alt": "La modalità di navigazione in incognito è attiva. Attiva per disattivare.", + "incognito-mode-label": "Modalità Incognito", + "next": "Prossimo", + "previous": "Precedente", + "go-to-page-prompt": "Ci sono {{totalPages}} pagine. A quale pagina vuoi andare?" }, "personal-table-of-contents": { - "no-data": "", - "page": "", - "delete": "" + "no-data": "Niente ancora aggiunto ai segnalibri", + "page": "Pagina {{value}}", + "delete": "Elimina {{bookmarkName}}" }, "confirm-email": { - "title": "", - "description": "", - "error-label": "", - "username-label": "", - "password-label": "", - "email-label": "", - "required-field": "", - "valid-email": "", - "password-validation": "", - "register": "" + "title": "Registrati", + "description": "Compila il form per completare la tua registrazione", + "error-label": "Errori: ", + "username-label": "{{common.username}}", + "password-label": "{{common.password}}", + "email-label": "{{common.email}}", + "required-field": "{{common.required-field}}", + "valid-email": "{{common.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "Registrati" }, "confirm-email-change": { - "title": "", - "non-confirm-description": "", - "confirm-description": "", - "success": "" + "title": "Convalida modifica e-mail", + "non-confirm-description": "Attendi mentre l'aggiornamento via email viene convalidato.", + "confirm-description": "La tua e-mail è stata convalidata e ora è stata modificata all'interno di Kavita. Verrai reindirizzato al login.", + "success": "Successo!" }, "confirm-reset-password": { - "title": "", - "description": "", - "password-label": "", - "required-field": "", - "submit": "", - "password-validation": "" + "title": "Reimpostazione della password", + "description": "Inserisci nuova password", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}" }, "register": { - "title": "", - "description": "", - "username-label": "", - "email-label": "", - "email-tooltip": "", - "password-label": "", - "required-field": "", - "valid-email": "", - "password-validation": "", - "register": "" + "title": "Registrati", + "description": "Completa il modulo per registrare un account amministratore", + "username-label": "{{common.username}}", + "email-label": "{{common.email}}", + "email-tooltip": "Non è necessario che l'e-mail sia un indirizzo reale, ma fornisce l'accesso alla password dimenticata. Non viene inviato all'esterno del server a meno che non venga utilizzata la password dimenticata senza un host del servizio di posta elettronica personalizzato.", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "Registra" }, "series-detail": { - "page-settings-title": "", - "close": "", - "layout-mode-label": "", - "layout-mode-option-card": "", - "layout-mode-option-list": "", - "continue-from": "", - "read": "", - "continue": "", - "read-options-alt": "", - "incognito": "", - "remove-from-want-to-read": "", - "add-to-want-to-read": "", - "edit-series-alt": "", - "download-series--tooltip": "", - "downloading-status": "", - "user-reviews-alt": "", - "storyline-tab": "", - "books-tab": "", - "volumes-tab": "", - "specials-tab": "", - "related-tab": "", - "recommendations-tab": "", - "send-to": "", - "no-pages": "", - "no-chapters": "", - "cover-change": "" + "page-settings-title": "Impostazioni della pagina", + "close": "{{common.close}}", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-option-card": "Scheda", + "layout-mode-option-list": "Lista", + "continue-from": "Continua {{title}}", + "read": "{{common.read}}", + "continue": "Continua", + "read-options-alt": "Opzioni di lettura", + "incognito": "Incongnito", + "remove-from-want-to-read": "Rimuovi dai vorrei leggere", + "add-to-want-to-read": "Aggiungi ai Vorrei Leggere", + "edit-series-alt": "Modifica informazione di serie", + "download-series--tooltip": "Scarica serie", + "downloading-status": "Scaricamento…", + "user-reviews-alt": "Recensioni degli utenti", + "storyline-tab": "Trama", + "books-tab": "Libri", + "volumes-tab": "Volumi", + "specials-tab": "Speciali", + "related-tab": "Collegati", + "recommendations-tab": "Consigli", + "send-to": "File inviato per email a {{deviceName}}", + "no-pages": "{{toasts.no-pages}}", + "no-chapters": "Non ci sono capitoli in questo volume. Impossibile leggere.", + "cover-change": "Puo servire piu di un minuto al tuo browser prima di ricaricare l'immagine. Fino ad allora, la vecchia immagine potrebbe essere mostrata su alcune pagine." }, "series-metadata-detail": { - "links-title": "", - "genres-title": "", - "tags-title": "", - "collections-title": "", - "reading-lists-title": "", - "writers-title": "", - "cover-artists-title": "", - "characters-title": "", - "colorists-title": "", - "editors-title": "", - "inkers-title": "", - "letterers-title": "", - "translators-title": "", - "pencillers-title": "", - "publishers-title": "", - "promoted": "", - "see-more": "", - "see-less": "" + "links-title": "Collegamenti", + "genres-title": "Generi", + "tags-title": "Tags", + "collections-title": "{{side-nav.collections}}", + "reading-lists-title": "{{side-nav.reading-lists}}", + "writers-title": "Scrittori", + "cover-artists-title": "Artisti della copertina", + "characters-title": "Personaggi", + "colorists-title": "Coloristi", + "editors-title": "Editrici", + "inkers-title": "Inchiostratori", + "letterers-title": "Letteristi", + "translators-title": "Traduttori", + "pencillers-title": "Illustratori", + "publishers-title": "Pubblicatori", + "promoted": "{{common.promoted}}", + "see-more": "Vedi ancora", + "see-less": "Vedi Meno" }, "badge-expander": { - "more-items": "" + "more-items": "e {{count}} ancora" }, "read-more": { - "read-more": "", - "read-less": "" + "read-more": "Leggi di piu", + "read-less": "Leggi meno" }, "update-notification-modal": { - "title": "", - "close": "", - "help": "", - "download": "" + "title": "Nuovo Aggiornamento Disponibile!", + "close": "{{common.close}}", + "help": "Come aggiornare", + "download": "Scaricamento" }, "side-nav-companion-bar": { - "page-settings-title": "", - "open-filter-and-sort": "", - "close-filter-and-sort": "", - "filter-and-sort-alt": "" + "page-settings-title": "{{series-detail.page-settings-title}}", + "open-filter-and-sort": "Apri filtro e ordinamento", + "close-filter-and-sort": "Chiudi filtraggio e ordinamento", + "filter-and-sort-alt": "Ordina / Filtra" }, "side-nav": { - "home": "", - "want-to-read": "", - "collections": "", - "reading-lists": "", - "bookmarks": "", - "filter-label": "", - "all-series": "", - "clear": "", - "donate": "" + "home": "Casa", + "want-to-read": "Voler leggere", + "collections": "Collezioni", + "reading-lists": "Liste di Lettura", + "bookmarks": "Preferiti", + "filter-label": "Filtro", + "all-series": "Tutte le Serie", + "clear": "Pulito", + "donate": "Donazione" }, "library-settings-modal": { - "close": "", - "edit-title": "", - "add-title": "", - "general-tab": "", - "folder-tab": "", - "cover-tab": "", - "advanced-tab": "", - "name-label": "", - "library-name-unique": "", - "last-scanned-label": "", - "type-label": "", - "type-tooltip": "", - "folder-description": "", - "browse": "", - "help-us-part-1": "", - "help-us-part-2": "", - "help-us-part-3": "", - "naming-conventions-part-1": "", - "naming-conventions-part-2": "", - "naming-conventions-part-3": "", - "cover-description": "", - "cover-description-extra": "", - "manage-collection-label": "", - "manage-collection-tooltip": "", - "manage-reading-list-label": "", - "manage-reading-list-tooltip": "", - "allow-scrobbling-label": "", - "allow-scrobbling-tooltip": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "include-in-dashboard-label": "", - "include-in-dashboard-tooltip": "", - "include-in-recommendation-label": "", - "include-in-recommendation-tooltip": "", - "include-in-search-label": "", - "include-in-search-tooltip": "", - "force-scan": "", - "force-scan-tooltip": "", - "reset": "", - "cancel": "", - "next": "", - "save": "", - "required-field": "" + "close": "{{common.close}}", + "edit-title": "Modifica {{name}}", + "add-title": "Aggiungi Libreria", + "general-tab": "Generale", + "folder-tab": "Cartella", + "cover-tab": "Copertina", + "advanced-tab": "Avanzato", + "name-label": "Nome", + "library-name-unique": "Il nome della Libreria deve essere univoco", + "last-scanned-label": "Ultima scansione:", + "type-label": "Tipo", + "type-tooltip": "Il tipo di libreria determina come vengono analizzati i nomi dei file e se l'interfaccia utente mostra capitoli (manga) e numeri (fumetti). Il libro funziona allo stesso modo del manga ma ha nomi diversi nell'interfaccia utente.", + "folder-description": "Aggiungi cartelle alla tua libreria", + "browse": "Cerca le cartelle multimediali", + "help-us-part-1": "Aiutaci seguendo ", + "help-us-part-2": "la nostra guida", + "help-us-part-3": "per nominare e organizzare i tuoi file multimediali.", + "naming-conventions-part-1": "Kavita ha ", + "naming-conventions-part-2": "requisiti della cartella.", + "naming-conventions-part-3": "Controlla questo link per assicurarti di seguire, altrimenti i file non vengono visualizzati nella scansione.", + "cover-description": "Le icone delle immagini della libreria personalizzata sono facoltative", + "cover-description-extra": "L'immagine della libreria non deve essere grande. Punta a un file piccolo, di dimensioni 32x32 pixel. Kavita non esegue la convalida delle dimensioni.", + "manage-collection-label": "Gestisci raccolte", + "manage-collection-tooltip": "Kavita dovrebbe creare raccolte dai tag SeriesGroup trovati nei file ComicInfo.xml/opf", + "manage-reading-list-label": "Gestisci gli elenchi di lettura", + "manage-reading-list-tooltip": "Kavita dovrebbe creare elenchi di lettura dai tag StoryArc/StoryArcNumber e AlternativeSeries/AlternativeCount trovati nei file ComicInfo.xml/opf", + "allow-scrobbling-label": "Consenti Scrobbling", + "allow-scrobbling-tooltip": "Se Kavita esegue lo scrobble degli eventi di lettura, desidera leggere lo stato, le valutazioni e le recensioni dei fornitori configurati. Ciò si verificherà solo se il server dispone di un abbonamento Kavita+ attivo.", + "folder-watching-label": "Controllo delle cartelle", + "folder-watching-tooltip": "Ignora il controllo della cartella del server per questa libreria. Se disattivato, il controllo delle cartelle non verrà eseguito sulle cartelle contenute in questa libreria. Se le librerie condividono le cartelle, è possibile che le cartelle vengano ancora confrontate.", + "include-in-dashboard-label": "Includi in Dashboard", + "include-in-dashboard-tooltip": "Le serie della biblioteca dovrebbero essere incluse nella Dashboard. Ciò riguarda tutti gli stream, come On Deck, Aggiornati di recente, Aggiunti di recente o qualsiasi aggiunta personalizzata.", + "include-in-recommendation-label": "Includi in Consigliato", + "include-in-recommendation-tooltip": "Le serie della biblioteca dovrebbero essere incluse nella pagina dei Consigliati.", + "include-in-search-label": "Includi nella ricerca", + "include-in-search-tooltip": "Le serie e qualsiasi informazione derivata (generi, persone, file) dalla biblioteca dovrebbero essere incluse nei risultati della ricerca.", + "force-scan": "Forza Scansione", + "force-scan-tooltip": "Questo forzerà una scansione sulla libreria, trattandola come una nuova scansione", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "next": "Prossimo", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}" }, "reader-settings": { - "general-settings-title": "", - "font-family-label": "", - "font-size-label": "", - "line-spacing-label": "", - "margin-label": "", - "reset-to-defaults": "", - "reader-settings-title": "", - "reading-direction-label": "", - "right-to-left": "", - "left-to-right": "", - "horizontal": "", - "vertical": "", - "writing-style-label": "", - "writing-style-tooltip": "", - "tap-to-paginate-label": "", - "tap-to-paginate-tooltip": "", - "on": "", - "off": "", - "immersive-mode-label": "", - "immersive-mode-tooltip": "", - "fullscreen-label": "", - "fullscreen-tooltip": "", - "exit": "", - "enter": "", - "layout-mode-label": "", - "layout-mode-tooltip": "", - "layout-mode-option-scroll": "", - "layout-mode-option-1col": "", - "layout-mode-option-2col": "", - "color-theme-title": "", - "theme-dark": "", - "theme-black": "", - "theme-white": "", - "theme-paper": "" + "general-settings-title": "Impostazioni Generali", + "font-family-label": "{{user-preferences.font-family-label}}", + "font-size-label": "{{user-preferences.font-size-book-label}}", + "line-spacing-label": "{{user-preferences.line-height-book-label}}", + "margin-label": "{{user-preferences.margin-book-label}}", + "reset-to-defaults": "Ripristina impostazioni predefinite", + "reader-settings-title": "Impostazioni Lettore", + "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "right-to-left": "Da destra a sinistra", + "left-to-right": "Da sinistra a destra", + "horizontal": "Orizzontale", + "vertical": "Verticale", + "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-tooltip": "Cambia la direzione del testo. Orizzontale è da sinistra a destra, verticale è dall'alto verso il basso.", + "tap-to-paginate-label": "Tocca Impaginazione", + "tap-to-paginate-tooltip": "Fare clic sui bordi dello schermo per impaginare", + "on": "Acceso", + "off": "Spento", + "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-tooltip": "Questo nasconderà il menu dietro un clic sul documento del lettore e attiverà il tocco per impaginare", + "fullscreen-label": "Schermo Intero", + "fullscreen-tooltip": "Metti il lettore in modalità a schermo intero", + "exit": "Esci", + "enter": "Entra", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-tooltip": "Scorrimento: rispecchia il file epub (di solito una lunga pagina a scorrimento per capitolo).
    1 Colonna: crea una singola pagina virtuale alla volta.
    2 Colonna: crea due pagine virtuali alla volta affiancate per lato.", + "layout-mode-option-scroll": "Scorrere", + "layout-mode-option-1col": "1 Colonna", + "layout-mode-option-2col": "2 Colonne", + "color-theme-title": "Colore Tema", + "theme-dark": "Scuro", + "theme-black": "Nero", + "theme-white": "Bianco", + "theme-paper": "Carta" }, "table-of-contents": { - "no-data": "" + "no-data": "Questo libro non ha un sommario impostato nei metadati o in un file toc" }, "bookmarks": { - "title": "", - "series-count": "", - "no-data": "", - "no-data-2": "", - "confirm-delete": "", - "confirm-single-delete": "", - "delete-success": "", - "delete-single-success": "" + "title": "{{side-nav.bookmarks}}", + "series-count": "{{common.series-count}}", + "no-data": "Non ci sono segnalibri. Prova a crearne", + "no-data-2": "uno.", + "confirm-delete": "Sei sicuro di voler cancellare tutti i segnalibri per più serie? Questo non può essere annullato.", + "confirm-single-delete": "Sei sicuro di voler cancellare tutti i segnalibri per {{seriesName}}? Questo non può essere annullato.", + "delete-success": "I segnalibri sono stati rimossi", + "delete-single-success": "I segnalibri di {{seriesName}} sono stati rimossi" }, "bulk-operations": { - "title": "", - "items-selected": "", - "mark-as-unread": "", - "mark-as-read": "", - "deselect-all": "" + "title": "Azione Collettiva", + "items-selected": "{{num}} elementi selezionati", + "mark-as-unread": "Segna come non letto", + "mark-as-read": "Segna come letto", + "deselect-all": "{{common.deselect-all}}" }, "card-detail-drawer": { - "general-tab": "", - "metadata-tab": "", - "cover-tab": "", - "info-tab": "", - "no-summary": "", - "writers-title": "", - "genres-title": "", - "publishers-title": "", - "tags-title": "", - "not-defined": "", - "read": "", - "unread": "", - "files": "", - "pages": "", - "added": "", - "size": "" + "general-tab": "Generale", + "metadata-tab": "Metadata", + "cover-tab": "Copertina", + "info-tab": "Informazioni", + "no-summary": "Nessun riepilogo disponibile.", + "writers-title": "{{series-metadata-detail.writers-title}}", + "genres-title": "{{series-metadata-detail.genres-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "not-defined": "Non definito", + "read": "{{common.read}}", + "unread": "Non letto", + "files": "File", + "pages": "Pagine:", + "added": "Aggiunto:", + "size": "Dimensione:" }, "card-detail-layout": { - "total-items": "" + "total-items": "{{count}} articoli totali" }, "card-item": { - "cannot-read": "" + "cannot-read": "Non posso leggere" }, "chapter-metadata-detail": { - "no-data": "", - "writers-title": "", - "publishers-title": "", - "characters-title": "", - "translators-title": "", - "letterers-title": "", - "colorists-title": "", - "inkers-title": "", - "pencillers-title": "", - "cover-artists-title": "", - "editors-title": "" + "no-data": "Nessuno netadata disponibile", + "writers-title": "{{series-metadata-detail.writers-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "translators-title": "{{series-metadata-detail.translators-title}}", + "letterers-title": "{{series-metadata-detail.letterers-title}}", + "colorists-title": "{{series-metadata-detail.colorists-title}}", + "inkers-title": "{{series-metadata-detail.inkers-title}}", + "pencillers-title": "{{series-metadata-detail.pencillers-title}}", + "cover-artists-title": "{{series-metadata-detail.cover-artists-title}}", + "editors-title": "{{series-metadata-detail.editors-title}}" }, "cover-image-chooser": { - "drag-n-drop": "", - "upload": "", - "upload-continued": "", - "url-label": "", - "load": "", - "back": "", - "reset-cover-tooltip": "", - "reset": "", - "image-num": "", - "apply": "", - "applied": "" + "drag-n-drop": "Trascina e rilascia", + "upload": "Caricamento", + "upload-continued": "una immagine", + "url-label": "Url", + "load": "Caricato", + "back": "Indietro", + "reset-cover-tooltip": "Ripristina immagine di copertina", + "reset": "{{common.reset}}", + "image-num": "Immagine {{num}}", + "apply": "{{common.apply}}", + "applied": "{{theme-manager.applied}}" }, "download-indicator": { - "progress": "" + "progress": "{{percentage}}% scaricato" }, "edit-series-relation": { - "description-part-1": "", - "description-part-2": "", - "target-series": "", - "relationship": "", - "remove": "", - "add-relationship": "", - "parent": "" + "description-part-1": "Non sei sicuro di quale relazione aggiungere? Vedi il nostro", + "description-part-2": "wiki per suggerimenti.", + "target-series": "Serie Target", + "relationship": "Relazione", + "remove": "Rimuovere", + "add-relationship": "Aggiungi Relazione", + "parent": "{{relationship-pipe.parent}}" }, "entity-info-cards": { - "tags-title": "", - "characters-title": "", - "release-date-title": "", - "release-date-tooltip": "", - "age-rating-title": "", - "length-title": "", - "pages-count": "", - "words-count": "", - "reading-time-title": "", - "date-added-title": "", - "size-title": "", - "id-title": "", - "links-title": "", - "isbn-title": "", - "last-read-title": "", - "less-than-hour": "", - "range-hours": "", - "hour": "", - "hours": "", - "read-time-title": "" + "tags-title": "{{series-metadata-detail.tags-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "release-date-title": "Pubblicazione", + "release-date-tooltip": "Data Pubblicazione", + "age-rating-title": "Valutazione età", + "length-title": "Lunghezza", + "pages-count": "{{num}} Pagine", + "words-count": "{{num}} Parole", + "reading-time-title": "Tempo Lettura", + "date-added-title": "Data Aggiunta", + "size-title": "Dimensione", + "id-title": "ID", + "links-title": "{{series-metadata-detail.links-title}}", + "isbn-title": "ISBN", + "last-read-title": "Ultimo Letto", + "less-than-hour": "<1 Ora", + "range-hours": "{{value}} {{hourWord}}", + "hour": "Ora", + "hours": "Ore", + "read-time-title": "{{series-info-cards.read-time-title}}" }, "series-info-cards": { - "release-date-title": "", - "release-year-tooltip": "", - "age-rating-title": "", - "language-title": "", - "publication-status-title": "", - "publication-status-tooltip": "", - "scrobbling-title": "", - "scrobbling-tooltip": "", - "on": "", - "off": "", - "disabled": "", - "format-title": "", - "last-read-title": "", - "length-title": "", - "read-time-title": "", - "less-than-hour": "", - "hour": "", - "hours": "", - "time-left-title": "", - "ongoing": "", - "pages-count": "", - "words-count": "" + "release-date-title": "{{entity-info-cards.release-date-title}}", + "release-year-tooltip": "Anno Pubblicazione", + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "language-title": "Lingua", + "publication-status-title": "Pubblicazione", + "publication-status-tooltip": "Stato Pubblicazione", + "scrobbling-title": "Scrobbling", + "scrobbling-tooltip": "Stato Scrobbling", + "on": "Acceso", + "off": "Spento", + "disabled": "Disabilitato", + "format-title": "Formato", + "last-read-title": "Ultimo Letto", + "length-title": "Lunghezza", + "read-time-title": "Tempo Lettura", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "time-left-title": "Tempo Rimasto", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}" }, "bulk-add-to-collection": { - "title": "", - "promoted": "", - "close": "", - "filter-label": "", - "clear": "", - "no-data": "", - "loading": "", - "collection-label": "", - "create": "" + "title": "Aggiungi alla Collezione", + "promoted": "{{common.promoted}}", + "close": "{{common.close}}", + "filter-label": "Filtro", + "clear": "{{common.clear}}", + "no-data": "Nessuna raccolta ancora creata", + "loading": "{{common.loading}}", + "collection-label": "Collezione", + "create": "{{common.create}}" }, "entity-title": { - "special": "", - "issue-num": "", - "chapter": "" + "special": "Speciale", + "issue-num": "Problema #", + "chapter": "Capitolo" }, "external-series-card": { - "open-external": "" + "open-external": "Apri Esternamente" }, "list-item": { - "read": "" + "read": "{{common.read}}" }, "manage-alerts": { - "description-part-1": "", - "description-part-2": "", - "filter-label": "", - "clear-alerts": "", - "extension-header": "", - "file-header": "", - "comment-header": "", - "details-header": "" + "description-part-1": "Questa tabella contiene i problemi rilevati durante la scansione o la lettura dei file multimediali. Questo elenco non è gestito. È possibile cancellarlo in qualsiasi momento e utilizzare la Scansione libreria (forzata) per eseguire l'analisi. Un elenco di alcuni errori comuni e del loro significato è disponibile sul file ", + "description-part-2": "wiki.", + "filter-label": "Filtro", + "clear-alerts": "Cancella Avvisi", + "extension-header": "Estensione", + "file-header": "File", + "comment-header": "Commento", + "details-header": "Dettagli" }, "manage-email-settings": { - "title": "", - "description": "", - "send-to-warning": "", - "email-url-label": "", - "email-url-tooltip": "", - "reset": "", - "test": "", - "host-name-label": "", - "host-name-tooltip": "", - "host-name-validation": "", - "reset-to-default": "", - "save": "" + "title": "Servizio Email (SMTP)", + "description": "Kavita è pronto all'uso con un servizio di posta elettronica per potenziare attività come invitare utenti, richieste di reimpostazione della password, ecc. Le e-mail inviate tramite il nostro servizio vengono eliminate immediatamente. Puoi utilizzare il tuo servizio di posta elettronica configurando il servizio {{link}}. Imposta l'URL del servizio di posta elettronica e utilizza il pulsante Prova per assicurarti che funzioni. Puoi ripristinare queste impostazioni predefinite in qualsiasi momento. Non è possibile disabilitare le e-mail per l'autenticazione, sebbene non sia necessario utilizzare un indirizzo e-mail valido per gli utenti. I collegamenti di conferma verranno sempre salvati nei log e presentati nell'interfaccia utente. Le e-mail di registrazione/conferma non verranno inviate se non accedi a Kavita tramite un URL raggiungibile pubblicamente o a meno che la funzione Nome host non sia configurata.", + "send-to-warning": "Se vuoi che Invia al dispositivo funzioni, devi ospitare il tuo servizio di posta elettronica.", + "email-url-label": "URL Servizio Email", + "email-url-tooltip": "Utilizza l'URL completo del servizio di posta elettronica. Non includere la barra finale.", + "reset": "{{common.reset}}", + "test": "Prova", + "host-name-label": "Nome Host", + "host-name-tooltip": "Nome di dominio (del proxy inverso). Se impostato, la generazione della posta elettronica utilizzerà sempre questo.", + "host-name-validation": "Il nome host deve iniziare con http(s) e non terminare con /", + "reset-to-default": "{{common.reset-to-default}}", + "save": "{{common.save}}" }, "manage-library": { - "title": "", - "add-library": "", - "no-data": "", - "loading": "", - "last-scanned-title": "", - "shared-folders-title": "", - "type-title": "", - "scan-library": "", - "delete-library": "", - "delete-library-by-name": "", - "edit-library": "", - "edit-library-by-name": "" + "title": "Librerie", + "add-library": "Aggiungi Libreria", + "no-data": "Non esistono librerie. Prova a crearne una.", + "loading": "{{common.loading}}", + "last-scanned-title": "Ultima scansione:", + "shared-folders-title": "Cartelle Condivise:", + "type-title": "Tipo:", + "scan-library": "Analizza Libreria", + "delete-library": "Elimina Libreria", + "delete-library-by-name": "Elimina {{name}}", + "edit-library": "Modifica", + "edit-library-by-name": "Elimina {{name}}" }, "manage-media-settings": { - "encode-as-description-part-1": "", - "encode-as-description-part-2": "", - "encode-as-description-part-3": "", - "encode-as-warning": "", - "media-warning": "", - "encode-as-label": "", - "encode-as-tooltip": "", - "bookmark-dir-label": "", - "bookmark-dir-tooltip": "", - "change": "", - "reset-to-default": "", - "reset": "", - "save": "", - "media-issue-title": "", - "scrobble-issue-title": "" + "encode-as-description-part-1": "WebP/AVIF puo drasticamente ridurre i requisiti di spazio per i file. WebP/AVIF non e supportato su tutti i browser o versioni. Per imparare se queste impostazioni sono appropriate per il tuo setup, visita ", + "encode-as-description-part-2": "Posso usare WebP?", + "encode-as-description-part-3": "Posso usare AVIF?", + "encode-as-warning": "Non puoi tornare in dietro una volta che sei passato a WebP/AVIF. Avresti bisogno di ricaricare le copertine sulle librerie per rigenerarle tutte. I segnalibri e favicons non possono essere convertiti.", + "media-warning": "È necessario attivare l'attività di conversione multimediale nella scheda Attività.,", + "encode-as-label": "Salva supporto con nome", + "encode-as-tooltip": "Tutti i media gestiti da Kavita (copertine, segnalibri, favicon) verranno codificati come questo tipo.", + "bookmark-dir-label": "Directory dei segnalibri", + "bookmark-dir-tooltip": "Posizione in cui verranno archiviati i segnalibri. I segnalibri sono file di origine e possono essere di grandi dimensioni. Scegli un luogo con spazio di archiviazione adeguato. La directory è gestita; gli altri file all'interno della directory verranno eliminati. Se Docker, monta un volume aggiuntivo e usalo.", + "change": "Cambia", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.reset}}", + "media-issue-title": "Problemi media", + "scrobble-issue-title": "Problemi di scrobble", + "cover-image-size-label": "Dimensioni dell'immagine di copertina", + "cover-image-size-tooltip": "Quanto deve essere grande la copertura delle immagini generate. Nota: qualsiasi cosa più grande di quella predefinita comporterà tempi di caricamento della pagina più lunghi." }, "manage-scrobble-errors": { - "description": "", - "filter-label": "", - "clear-errors": "", - "series-header": "", - "created-header": "", - "comment-header": "", - "edit-header": "", - "edit-item-alt": "" + "description": "Questa tabella contiene i problemi rilevati durante lo scrobbling. Questo elenco non è gestito. Puoi cancellarlo in qualsiasi momento e attendere il successivo caricamento di scrobble per vederlo. Se è presente una serie sconosciuta, è meglio correggere il nome della serie o il nome della serie localizzata o aggiungere un collegamento web per i fornitori.", + "filter-label": "Filtro", + "clear-errors": "Pulisic Errori", + "series-header": "Serie", + "created-header": "Creata", + "comment-header": "Commento", + "edit-header": "Modifica", + "edit-item-alt": "Modifica {{seriesName}}" }, "default-date-pipe": { - "never": "" + "never": "Mai" }, "manage-settings": { - "notice": "", - "restart-required": "", - "base-url-label": "", - "base-url-tooltip": "", - "ip-address-label": "", - "ip-address-tooltip": "", - "port-label": "", - "port-tooltip": "", - "backup-label": "", - "backup-tooltip": "", - "log-label": "", - "log-tooltip": "", - "logging-level-label": "", - "logging-level-tooltip": "", - "cache-size-label": "", - "cache-size-tooltip": "", - "on-deck-last-progress-label": "", - "on-deck-last-progress-tooltip": "", - "on-deck-last-chapter-add-label": "", - "on-deck-last-chapter-add-tooltip": "", - "allow-stats-label": "", - "allow-stats-tooltip-part-1": "", - "allow-stats-tooltip-part-2": "", - "send-data": "", - "opds-label": "", - "opds-tooltip": "", - "enable-opds": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "enable-folder-watching": "", - "reset-to-default": "", - "reset": "", - "save": "", - "cache-size-validation": "", - "field-required": "", - "max-logs-validation": "", - "min-logs-validation": "", - "min-days-validation": "", - "min-cache-validation": "", - "max-backup-validation": "", - "min-backup-validation": "", - "ip-address-validation": "", - "base-url-validation": "" + "notice": "Avviso:", + "restart-required": "La modifica di porta, URL di base, dimensione della cache o IP richiede il riavvio manuale di Kavita per avere effetto.", + "base-url-label": "Url Base", + "base-url-tooltip": "Usalo se vuoi ospitare Kavita su un URL di base, ad esempio) tuodominio.com/kavita. Non supportato su Docker utilizzando un utente non root.", + "ip-address-label": "Indirizzo IP", + "ip-address-tooltip": "Elenco separato da virgole di indirizzi IP su cui il server è in ascolto. Il problema viene risolto se si esegue Docker. Richiede il riavvio per avere effetto.", + "port-label": "Porta", + "port-tooltip": "Porta su cui è in ascolto il server. Il problema viene risolto se si esegue Docker. Richiede il riavvio per avere effetto.", + "backup-label": "Giorni del Backup", + "backup-tooltip": "Il numero di backup da mantenere. Il valore predefinito è 30, il minimo è 1, il massimo è 30.", + "log-label": "Giorni del Log", + "log-tooltip": "Il numero di log da mantenere. Il valore predefinito è 30, il minimo è 1, il massimo è 30.", + "logging-level-label": "Livello di Log", + "logging-level-tooltip": "Utilizza il debug per identificare i problemi. Il debug può consumare molto spazio su disco.", + "cache-size-label": "Dimensione Cache", + "cache-size-tooltip": "La quantità di memoria consentita per la memorizzazione nella cache di API pesanti. L'impostazione predefinita è 75 MB.", + "on-deck-last-progress-label": "Sul ponte Ultimo progresso (giorni)", + "on-deck-last-progress-tooltip": "Il numero di giorni trascorsi dall'ultimo progresso prima di avviare qualcosa su On Deck.", + "on-deck-last-chapter-add-label": "Sul ponte Aggiunta ultimo capitolo (giorni)", + "on-deck-last-chapter-add-tooltip": "Il numero di giorni trascorsi dall'ultimo capitolo è stato aggiunto per includere qualcosa sul ponte.", + "allow-stats-label": "Consenti raccolta utilizzo anonimo", + "allow-stats-tooltip-part-1": "Invia dati di utilizzo anonimi ai server di Kavita. Ciò include informazioni su alcune funzionalità utilizzate, numero di file, versione del sistema operativo, versione di installazione di Kavita, CPU e memoria. Utilizzeremo queste informazioni per dare priorità a funzionalità, correzioni di bug e ottimizzazione delle prestazioni. Richiede il riavvio per avere effetto. Vedi il ", + "allow-stats-tooltip-part-2": "per quanto raccolto.", + "send-data": "Invia dati", + "opds-label": "OPDS", + "opds-tooltip": "Il supporto OPDS consentirà a tutti gli utenti di utilizzare OPDS per leggere e scaricare contenuti dal server.", + "enable-opds": "Abilita OPDS", + "folder-watching-label": "Controllo delle cartelle", + "folder-watching-tooltip": "Consente a Kavita di monitorare le cartelle della libreria per rilevare le modifiche e richiamare la scansione su tali modifiche. Ciò consente di aggiornare il contenuto senza richiamare manualmente le scansioni o attendere le scansioni notturne.", + "enable-folder-watching": "Abilita controllo delle cartelle", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "Devi avere almeno 50 MB.", + "field-required": "{{validation.field-required}}", + "max-logs-validation": "Non puoi avere più di {{num}} log", + "min-logs-validation": "Devi avere almeno 1 log", + "min-days-validation": "Deve essere almeno 1 giorno", + "min-cache-validation": "Deve essere a 50 MB.", + "max-backup-validation": "Non puoi avere più di {{num}} backup", + "min-backup-validation": "Devi avere almeno 1 backup", + "ip-address-validation": "Gli indirizzi IP possono contenere solo indirizzi IPv4 o IPv6 validi", + "base-url-validation": "L'URL di base deve iniziare e terminare con /" }, "manage-system": { - "title": "", - "version-title": "", - "installId-title": "", - "more-info-title": "", - "home-page-title": "", - "wiki-title": "", - "discord-title": "", - "donations-title": "", - "source-title": "", - "feature-request-title": "" + "title": "Informazioni sul sistema", + "version-title": "Versione", + "installId-title": "ID Installazione", + "more-info-title": "Altre Informazioni", + "home-page-title": "Pagina iniziale:", + "wiki-title": "Wiki:", + "discord-title": "Discord:", + "donations-title": "Donazioni:", + "source-title": "Sorgente:", + "feature-request-title": "Richieste di funzionalità" }, "manage-tasks-settings": { - "title": "", - "library-scan-label": "", - "library-scan-tooltip": "", - "library-database-backup-label": "", - "library-database-backup-tooltip": "", - "adhoc-tasks-title": "", - "job-title-header": "", - "description-header": "", - "action-header": "", - "reset-to-default": "", - "reset": "", - "save": "", - "recurring-tasks-title": "", - "last-executed-header": "", - "cron-header": "", - "convert-media-task": "", - "convert-media-task-desc": "", - "convert-media-success": "", - "bust-cache-task": "", - "bust-cache-task-desc": "", - "bust-cache-task-success": "", - "clear-reading-cache-task": "", - "clear-reading-cache-task-desc": "", - "clear-reading-cache-task-success": "", - "clean-up-want-to-read-task": "", - "clean-up-want-to-read-task-desc": "", - "clean-up-want-to-read-task-success": "", - "backup-database-task": "", - "backup-database-task-desc": "", - "backup-database-task-success": "", - "download-logs-task": "", - "download-logs-task-desc": "", - "analyze-files-task": "", - "analyze-files-task-desc": "", - "analyze-files-task-success": "", - "check-for-updates-task": "", - "check-for-updates-task-desc": "" + "title": "Task ricorrenti", + "library-scan-label": "Scansione Libreria", + "library-scan-tooltip": "La frequenza con cui Kavita scansionerà e aggiornerà i metadati sui file della libreria.", + "library-database-backup-label": "Backup del database della libreria", + "library-database-backup-tooltip": "La frequenza con cui Kavita eseguirà il backup del database.", + "adhoc-tasks-title": "Compiti ad hoc", + "job-title-header": "Titolo di lavoro", + "description-header": "Descrizione", + "action-header": "Azione", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}", + "last-executed-header": "Ultimo eseguito", + "cron-header": "Cron", + "convert-media-task": "Converti i media nella codifica di destinazione", + "convert-media-task-desc": "Esegue un'attività a lunga esecuzione che convertirà tutti i media gestiti da Kavita nella codifica di destinazione. Questo è lento (specialmente sui dispositivi ARM).", + "convert-media-success": "La conversione dei contenuti multimediali nella codifica target è stata messa in coda", + "bust-cache-task": "Cache Danneggiata", + "bust-cache-task-desc": "Elimina la cache Kavita+: dovrebbe essere utilizzato solo durante il debug di corrispondenze errate.", + "bust-cache-task-success": "Cache Kavita+ danneggiata", + "clear-reading-cache-task": "Pulisci Cache di Lettura", + "clear-reading-cache-task-desc": "Cancella i file memorizzati nella cache per la lettura. Utile quando hai appena aggiornato un file che stavi leggendo nelle ultime 24 ore.", + "clear-reading-cache-task-success": "La cache è stata svuotata", + "clean-up-want-to-read-task": "Pulisci Vuoi leggere", + "clean-up-want-to-read-task-desc": "Rimuove tutte le serie che gli utenti hanno letto completamente che si trovano in Da leggere e hanno uno stato di pubblicazione Completato. Funziona ogni 24 ore.", + "clean-up-want-to-read-task-success": "Voglio leggere è stato ripulito", + "backup-database-task": "Backup Database", + "backup-database-task-desc": "Esegue un backup del database, dei segnalibri, dei temi, delle copertine caricate manualmente e dei file di configurazione.", + "backup-database-task-success": "Un lavoro per il backup del database è stato messo in coda", + "download-logs-task": "Scarica Logs", + "download-logs-task-desc": "Compila tutti i file di registro in un file zip e lo scarica.", + "analyze-files-task": "Analizza File", + "analyze-files-task-desc": "Esegue un'attività di lunga durata che analizzerà i file per generare estensione e dimensione. Dovrebbe essere eseguito solo una volta per la versione v0.7. Non necessario se hai installato la versione successiva alla v0.7.", + "analyze-files-task-success": "L'analisi del file è stata messa in coda", + "check-for-updates-task": "Cerca Aggiornamenti", + "check-for-updates-task-desc": "Verifica se sono disponibili versioni stabili prima della tua versione." }, "manage-users": { - "title": "", - "invite": "", - "you-alt": "", - "pending-title": "", - "delete-user-tooltip": "", - "delete-user-alt": "", - "edit-user-tooltip": "", - "edit-user-alt": "", - "resend-invite-tooltip": "", - "resend-invite-alt": "", - "setup-user-tooltip": "", - "setup-user-alt": "", - "change-password-tooltip": "", - "change-password-alt": "", - "resend": "", - "setup": "", - "last-active-title": "", - "roles-title": "", - "none": "", - "never": "", - "online-now-tooltip": "", - "sharing-title": "", - "no-data": "", - "loading": "" + "title": "Utenti Attivi", + "invite": "Inviti", + "you-alt": "(Tu)", + "pending-title": "In attesa di", + "delete-user-tooltip": "Elimina utente", + "delete-user-alt": "Elimina utente {{user}}", + "edit-user-tooltip": "Modifica", + "edit-user-alt": "Modifica Utente {{user}}", + "resend-invite-tooltip": "Rimanda Invito", + "resend-invite-alt": "Rimanda Invito {{user}}", + "setup-user-tooltip": "Imposta utente", + "setup-user-alt": "Imposta utente {{user}}", + "change-password-tooltip": "Cambia Password", + "change-password-alt": "Cambia Password {{user}}", + "resend": "Rinvia", + "setup": "Impostare", + "last-active-title": "Ultimo attivo:", + "roles-title": "Regole:", + "none": "Nessuno", + "never": "Mai", + "online-now-tooltip": "Inlinea Adesso", + "sharing-title": "Condivisione:", + "no-data": "Non ci sono altri utenti.", + "loading": "{{common.loading}}" }, "edit-collection-tags": { - "title": "", - "required-field": "", - "save": "", - "close": "", - "cancel": "", - "general-tab": "", - "cover-image-tab": "", - "series-tab": "", - "name-label": "", - "name-validation": "", - "promote-label": "", - "promote-tooltip": "", - "summary-label": "", - "series-title": "", - "deselect-all": "", - "select-all": "" + "title": "Modifica {{collectionName}} Collezione", + "required-field": "{{validation.required-field}}", + "save": "{{common.save}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "general-tab": "Generale", + "cover-image-tab": "Immagine Copertina", + "series-tab": "Serie", + "name-label": "Nome", + "name-validation": "Il nome deve essere univoco", + "promote-label": "Promuovi", + "promote-tooltip": "La promozione significa che il tag può essere visto a livello di server, non solo per gli utenti amministratori. Su tutte le serie che hanno questo tag verranno comunque applicate restrizioni di accesso da parte degli utenti.", + "summary-label": "Sommario", + "series-title": "Applica alle Serie", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}" }, "library-detail": { - "library-tab": "", - "recommended-tab": "" + "library-tab": "Libreria", + "recommended-tab": "Raccomandato" }, "library-recommended": { - "no-data": "", - "more-in-genre": "", - "rediscover": "", - "highly-rated": "", - "quick-catchups": "", - "quick-reads": "", - "on-deck": "" + "no-data": "Niente da mostrare qui. Aggiungi alcuni metadati alla tua libreria, leggi qualcosa o valuta qualcosa. In questa libreria potrebbero anche essere disattivati i consigli.", + "more-in-genre": "Altro in {{genere}}", + "rediscover": "Riscopri", + "highly-rated": "Altamente valutato", + "quick-catchups": "Recuperi rapidi", + "quick-reads": "Letture veloci", + "on-deck": "{{dashboard.on-deck-title}}" }, "admin-dashboard": { - "title": "", - "general-tab": "", - "users-tab": "", - "libraries-tab": "", - "media-tab": "", - "logs-tab": "", - "email-tab": "", - "tasks-tab": "", - "statistics-tab": "", - "system-tab": "", - "kavita+-tab": "", - "kavita+-desc-part-1": "", - "kavita+-desc-part-2": "", - "kavita+-desc-part-3": "" + "title": "Pannello di amministrazione", + "general-tab": "Generale", + "users-tab": "Utenti", + "libraries-tab": "Librerie", + "media-tab": "Media", + "logs-tab": "Logs", + "email-tab": "Email", + "tasks-tab": "Tasks", + "statistics-tab": "Statistiche", + "system-tab": "Sistema", + "kavita+-tab": "Kavita+", + "kavita+-desc-part-1": "Kavita+ è un servizio in abbonamento premium che sblocca funzionalità per tutti gli utenti su questa istanza Kavita. Acquista un abbonamento per sbloccare ", + "kavita+-desc-part-2": "vantaggi premium", + "kavita+-desc-part-3": "oggi!" }, "collection-detail": { - "no-data": "", - "no-data-filtered": "", - "title-alt": "" + "no-data": "Non ci sono articoli. Prova ad aggiungere una serie.", + "no-data-filtered": "Nessun articolo corrisponde al filtro attuale.", + "title-alt": "Kavita - Collezione {{collectionName}}" }, "all-collections": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "Collezioni", + "item-count": "{{common.item-count}}", + "no-data": "Non ci sono collezioni.", + "create-one-part-1": "Prova a creare", + "create-one-part-2": "uno" }, "carousel-reel": { - "prev-items": "", - "next-items": "" + "prev-items": "Articoli precedenti", + "next-items": "Articoli successivi" }, "draggable-ordered-list": { - "instructions-alt": "", - "reorder-label": "", - "remove-item-alt": "" + "instructions-alt": "Quando inserisci un numero nell'input di riordino, l'articolo verrà inserito in quella posizione e tutti gli altri articoli avranno il loro ordine aggiornato.", + "reorder-label": "Riordina", + "remove-item-alt": "Rimuovi articoli" }, "reading-lists": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "Liste Letture", + "item-count": "{{common.item-count}}", + "no-data": "Non ci sono elenchi di lettura.", + "create-one-part-1": "Prova a creare", + "create-one-part-2": "uno" }, "reading-list-item": { - "remove": "", - "read": "" + "remove": "{{common.remove}}", + "read": "{{common.read}}" }, "reading-list-detail": { - "item-count": "", - "page-settings-title": "", - "remove-read": "", - "order-numbers-label": "", - "continue": "", - "read": "", - "read-options-alt": "", - "incognito-alt": "", - "no-data": "" + "item-count": "{{common.item-count}}", + "page-settings-title": "Impostazioni Pagina", + "remove-read": "Rimuovi Lettura", + "order-numbers-label": "Numeri d'ordine", + "continue": "Continua", + "read": "{{common.read}}", + "read-options-alt": "Opzioni Lettura", + "incognito-alt": "(Incognito)", + "no-data": "Nulla aggiunto", + "characters-title": "{{series-metadata-detail.characters-title}}" }, "events-widget": { - "title-alt": "", - "dismiss-all": "", - "update-available": "", - "downloading-item": "", - "more-info": "", - "close": "", - "users-online-count": "", - "active-events-title": "", - "no-data": "" + "title-alt": "Attività", + "dismiss-all": "Ignora tutto", + "update-available": "Aggiornamento disponibile", + "downloading-item": "Download di {{item}}", + "more-info": "Fare clic per ulteriori informazioni", + "close": "{{common.close}}", + "users-online-count": "{{num}} Utenti online", + "active-events-title": "Eventi Attivi:", + "no-data": "Non succede molto qui" }, "shortcuts-modal": { - "title": "", - "close": "", - "prev-page": "", - "next-page": "", - "go-to": "", - "bookmark": "", - "double-click": "", - "close-reader": "", - "toggle-menu": "" + "title": "Scorciatoie Tastiera", + "close": "{{common.close}}", + "prev-page": "Muovi alla pagina precedente", + "next-page": "Muovi alla pagina successiva", + "go-to": "Apri la finestra di dialogo Vai alla pagina", + "bookmark": "Aggiungi la pagina corrente ai segnalibri", + "double-click": "doppio click", + "close-reader": "Chiudi Lettore", + "toggle-menu": "Cambia menu" }, "grouped-typeahead": { - "files": "", - "chapters": "", - "people": "", - "tags": "", - "genres": "", - "libraries": "", - "reading-lists": "", - "collections": "", - "close": "", - "loading": "" + "files": "File", + "chapters": "Capitoli", + "people": "Persone", + "tags": "Tags", + "genres": "Genere", + "libraries": "Librerie", + "reading-lists": "Liste Lettura", + "collections": "Collezioni", + "close": "{{common.close}}", + "loading": "{{common.loading}}" }, "nav-header": { - "skip-alt": "", - "search-series-alt": "", - "search-alt": "", - "promoted": "", - "no-data": "", - "scroll-to-top-alt": "", - "server-settings": "", - "settings": "", - "help": "", - "announcements": "", - "logout": "" + "skip-alt": "Passa al contenuto principale", + "search-series-alt": "Cerca Serie", + "search-alt": "Ricerca…", + "promoted": "(promossa)", + "no-data": "Nessun risultato trovato", + "scroll-to-top-alt": "Scorri verso l'alto", + "server-settings": "Impostazioni del server", + "settings": "Impostazioni", + "help": "Aiuto", + "announcements": "Annunci", + "logout": "Disconnettersi" }, "add-to-list-modal": { - "title": "", - "close": "", - "filter-label": "", - "promoted-alt": "", - "no-data": "", - "loading": "", - "reading-list-label": "", - "create": "" + "title": "Aggiungi a Lista Lettura", + "close": "{{common.close}}", + "filter-label": "Filtro", + "promoted-alt": "Promossa", + "no-data": "Nessuna lista ancora creata", + "loading": "{{common.loading}}", + "reading-list-label": "Lista Lettura", + "create": "{{common.create}}" }, "edit-reading-list-modal": { - "title": "", - "general-tab": "", - "cover-image-tab": "", - "close": "", - "save": "", - "year-validation": "", - "month-validation": "", - "name-unique-validation": "", - "required-field": "", - "summary-label": "", - "year-label": "", - "month-label": "", - "ending-title": "", - "starting-title": "", - "promote-label": "", - "promote-tooltip": "" + "title": "Modifica Lista Lettura: {{name}}", + "general-tab": "Generale", + "cover-image-tab": "Immagine Copertina", + "close": "{{common.close}}", + "save": "{common.save}}", + "year-validation": "Deve essere maggiore di 1000, 0 o vuoto", + "month-validation": "Deve essere compreso tra 1 e 12 o vuoto", + "name-unique-validation": "Il nome deve essere univoco", + "required-field": "{{validation.required-field}}", + "summary-label": "Sommario", + "year-label": "Anno", + "month-label": "Mese", + "ending-title": "Fine", + "starting-title": "Inizio", + "promote-label": "Promuovere", + "promote-tooltip": "La promozione significa che il tag può essere visto a livello di server, non solo per gli utenti amministratori. Su tutte le serie che hanno questo tag verranno comunque applicate restrizioni di accesso da parte degli utenti." }, "import-cbl-modal": { - "close": "", - "title": "", - "import-description": "", - "validate-description": "", - "validate-warning": "", - "validate-no-issue": "", - "validate-no-issue-description": "", - "dry-run-description": "", - "prev": "", - "import": "", - "restart": "", - "next": "", - "import-step": "", - "validate-cbl-step": "", - "dry-run-step": "", - "final-import-step": "" + "close": "{{common.close}}", + "title": "Importa CBL", + "import-description": "Per iniziare, importa un file .cbl. Kavita eseguirà più controlli prima dell'importazione. Alcuni passaggi bloccheranno l'avanzamento a causa di problemi con il file.", + "validate-description": "Tutti i file sono stati convalidati per vedere se ci sono operazioni da fare nell'elenco. Eventuali elenchi non riusciti non passeranno al passaggio successivo. Correggi i file CBL e riprova.", + "validate-warning": "Ci sono problemi con il CBL che impediscono l'importazione. Correggi questi problemi e riprova.", + "validate-no-issue": "Sembra buono", + "validate-no-issue-description": "Nessun problema riscontrato con CBL, premi Avanti.", + "dry-run-description": "Questa è una prova e mostra cosa accadrà se premi Avanti ed esegui l'importazione. Tutti gli errori non verranno importati.", + "prev": "Prec", + "import": "Importa", + "restart": "Riavvia", + "next": "Prossimo", + "import-step": "Importa CBL", + "validate-cbl-step": "Convalidare CBL", + "dry-run-step": "Fai una prova", + "final-import-step": "Passaggio Finale" }, "pdf-reader": { - "loading-message": "", - "incognito-mode": "", - "light-theme-alt": "", - "dark-theme-alt": "", - "close-reader-alt": "" - }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" + "loading-message": "Caricamento... I PDF potrebbero richiedere più tempo del previsto", + "incognito-mode": "Modalità Incognito", + "light-theme-alt": "Tema Chiaro", + "dark-theme-alt": "Tema Scuro", + "close-reader-alt": "Chiudi Lettore" }, "manga-reader": { - "back": "", - "save-globally": "", - "incognito-alt": "", - "incognito-title": "", - "shortcuts-menu-alt": "", - "prev-page-tooltip": "", - "next-page-tooltip": "", - "prev-chapter-tooltip": "", - "next-chapter-tooltip": "", - "first-page-tooltip": "", - "last-page-tooltip": "", - "left-to-right-alt": "", - "right-to-left-alt": "", - "reading-direction-tooltip": "", - "reading-mode-tooltip": "", - "collapse": "", - "fullscreen": "", - "settings-tooltip": "", - "image-splitting-label": "", - "image-scaling-label": "", - "height": "", - "width": "", - "original": "", - "auto-close-menu-label": "", - "swipe-enabled-label": "", - "enable-comic-book-label": "", - "brightness-label": "", - "first-time-reading-manga": "", - "layout-mode-switched": "", - "no-next-chapter": "", - "no-prev-chapter": "", - "user-preferences-updated": "" + "back": "Indietro", + "save-globally": "Salva Globalmente", + "incognito-alt": "La modalità di navigazione in incognito è attiva. Attiva/disattiva per disattivare.", + "incognito-title": "Modalità Incognito:", + "shortcuts-menu-alt": "Scorciatoie da tastiera modale", + "prev-page-tooltip": "Pagina Precedente", + "next-page-tooltip": "Pagina Successiva", + "prev-chapter-tooltip": "Prec Capitolo/Volume", + "next-chapter-tooltip": "Prossimo Capitolo/Volume", + "first-page-tooltip": "Prima Pagina", + "last-page-tooltip": "Ultima Pagina", + "left-to-right-alt": "Da sinistra a destra", + "right-to-left-alt": "Da destra a sinistra", + "reading-direction-tooltip": "Direzione Lettura ", + "reading-mode-tooltip": "Modalità di Lettura", + "collapse": "Collassa", + "fullscreen": "Schermo Intero", + "settings-tooltip": "Impostazioni", + "image-splitting-label": "Divisione delle immagini", + "image-scaling-label": "Ridimensionamento dell'immagine", + "height": "Altezza", + "width": "Larghezza", + "original": "Originale", + "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "swipe-enabled-label": "Scorrimento abilitato", + "enable-comic-book-label": "Emulazione Fumetto", + "brightness-label": "Luminosità", + "first-time-reading-manga": "Tocca l'immagine in qualsiasi momento per aprire il menu. È possibile configurare diverse impostazioni o andare alla pagina facendo clic sulla barra di avanzamento. Tocca i lati dell'immagine per passare alla pagina successiva/precedente.", + "layout-mode-switched": "La modalità layout è passata a Singola a causa dello spazio insufficiente per eseguire il rendering del layout doppio", + "no-next-chapter": "Nessun prossimo capitolo", + "no-prev-chapter": "Nessun capitolo precedente", + "user-preferences-updated": "Preferenze utente aggiornate", + "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}" }, "metadata-filter": { - "filter-title": "", - "format-label": "", - "format-tooltip": "", - "libraries-label": "", - "collections-label": "", - "genres-label": "", - "tags-label": "", - "cover-artist-label": "", - "writer-label": "", - "publisher-label": "", - "penciller-label": "", - "letterer-label": "", - "inker-label": "", - "editor-label": "", - "colorist-label": "", - "character-label": "", - "translator-label": "", - "read-progress-label": "", - "unread": "", - "read": "", - "in-progress": "", - "rating-label": "", - "age-rating-label": "", - "language-label": "", - "publication-status-label": "", - "series-name-label": "", - "series-name-tooltip": "", - "release-label": "", - "min": "", - "max": "", - "sort-by-label": "", - "ascending-alt": "", - "descending-alt": "", - "reset": "", - "apply": "" + "filter-title": "Filtro", + "format-label": "Formato", + "libraries-label": "Librerie", + "collections-label": "Collezioni", + "genres-label": "Genere", + "tags-label": "Tags", + "cover-artist-label": "Artista Copertina", + "writer-label": "Scrittore", + "publisher-label": "Editore", + "penciller-label": "Disegnatore", + "letterer-label": "Letterista", + "inker-label": "Inchiostratore", + "editor-label": "Editore", + "colorist-label": "Colorista", + "character-label": "Carattere", + "translator-label": "Traduttore", + "read-progress-label": "Progresso Lettura", + "unread": "Non letto", + "read": "Leggere", + "in-progress": "In corso", + "rating-label": "Valutazione", + "age-rating-label": "Valutazione dell'età", + "language-label": "Lingua", + "publication-status-label": "Stato Pubblicazione", + "series-name-label": "Nome Serie", + "series-name-tooltip": "Il nome della serie verrà filtrato in base a Nome, Nome ordinamento o Nome localizzato", + "release-label": "Pubblicazione", + "min": "Min", + "max": "Max", + "sort-by-label": "Ordina per", + "ascending-alt": "Ascendente", + "descending-alt": "Discendente", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "limit-label": "Limita a" }, "sort-field-pipe": { - "sort-name": "", - "created": "", - "last-modified": "", - "last-chapter-added": "", - "time-to-read": "", - "release-year": "" + "sort-name": "Ordina Nome", + "created": "Creato", + "last-modified": "Ultima modifica", + "last-chapter-added": "Elemento aggiunto", + "time-to-read": "Tempo di leggere", + "release-year": "Anno di pubblicazione" }, "edit-series-modal": { - "title": "", - "general-tab": "", - "metadata-tab": "", - "people-tab": "", - "web-links-tab": "", - "cover-image-tab": "", - "related-tab": "", - "info-tab": "", - "collections-label": "", - "genres-label": "", - "tags-label": "", - "cover-artist-label": "", - "writer-label": "", - "publisher-label": "", - "penciller-label": "", - "letterer-label": "", - "inker-label": "", - "editor-label": "", - "colorist-label": "", - "character-label": "", - "translator-label": "", - "language-label": "", - "age-rating-label": "", - "publication-status-label": "", - "required-field": "", - "close": "", - "name-label": "", - "sort-name-label": "", - "localized-name-label": "", - "summary-label": "", - "release-year-label": "", - "web-link-description": "", - "web-link-label": "", - "add-link-alt": "", - "remove-link-alt": "", - "cover-image-description": "", - "save": "", - "field-locked-alt": "", - "info-title": "", - "library-title": "", - "format-title": "", - "created-title": "", - "last-read-title": "", - "last-added-title": "", - "last-scanned-title": "", - "folder-path-title": "", - "publication-status-title": "", - "total-pages-title": "", - "total-items-title": "", - "max-items-title": "", - "size-title": "", - "loading": "", - "added-title": "", - "last-modified-title": "", - "view-files": "", - "pages-title": "", - "chapter-title": "", - "volume-num": "", - "highest-count-tooltip": "", - "max-issue-tooltip": "" + "title": "{{seriesName}} Dettagli", + "general-tab": "Generale", + "metadata-tab": "Metadata", + "people-tab": "Persone", + "web-links-tab": "Link internet", + "cover-image-tab": "Immagine Copertina", + "related-tab": "Relativo a", + "info-tab": "Info", + "collections-label": "Collezioni", + "genres-label": "Generi", + "tags-label": "Tags", + "cover-artist-label": "Artista Copertina", + "writer-label": "Scrittore", + "publisher-label": "Editrice", + "penciller-label": "Disegnatore", + "letterer-label": "Letterista", + "inker-label": "Inchiostratore", + "editor-label": "Editore", + "colorist-label": "Colorista", + "character-label": "Carattere", + "translator-label": "Traduttore", + "language-label": "Lingua", + "age-rating-label": "Valutazione dell'età", + "publication-status-label": "Stato della pubblicazione", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "name-label": "Nome", + "sort-name-label": "Ordina nome", + "localized-name-label": "Nome localizzato", + "summary-label": "Sommario", + "release-year-label": "Anno di pubblicazione", + "web-link-description": "Qui puoi aggiungere molti collegamenti diversi a servizi esterni.", + "web-link-label": "Collegamento Internet", + "add-link-alt": "Aggiungi collegamento", + "remove-link-alt": "Rimuovi Collegamento", + "cover-image-description": "Carica e scegli una nuova immagine di copertina. Premi Salva per caricare e sovrascrivere la copertina.", + "save": "{{common.save}}", + "field-locked-alt": "Il campo è bloccato", + "info-title": "Informazioni", + "library-title": "Biblioteca:", + "format-title": "Formato:", + "created-title": "Creato:", + "last-read-title": "Ultima lettura:", + "last-added-title": "Ultimo elemento aggiunto:", + "last-scanned-title": "Ultima scansione:", + "folder-path-title": "Percorso Cartella:", + "publication-status-title": "Stato Pubblicazione:", + "total-pages-title": "Pagine Totali:", + "total-items-title": "Articoli totali:", + "max-items-title": "Articoli massimi:", + "size-title": "Dimensione:", + "loading": "{{common.loading}}", + "added-title": "Aggiunto:", + "last-modified-title": "Ultima modifica:", + "view-files": "Vedi files", + "pages-title": "Pagine:", + "chapter-title": "Capitolo:", + "volume-num": "{{common.volume-num}}", + "highest-count-tooltip": "Conteggio più alto trovato in tutte le ComicInfo della serie", + "max-issue-tooltip": "Campo numero massimo o volume da tutti i ComicInfo della serie" }, "day-breakdown": { - "title": "", - "x-axis-label": "", - "y-axis-label": "" + "title": "Ripartizione del giorno", + "x-axis-label": "Giorno della settimana", + "y-axis-label": "Eventi di lettura" }, "file-breakdown-stats": { - "format-title": "", - "format-tooltip": "", - "visualisation-label": "", - "data-table-label": "", - "extension-header": "", - "format-header": "", - "total-size-header": "", - "total-files-header": "", - "not-classified": "", - "total-file-size-title": "" + "format-title": "Formato", + "format-tooltip": "Non classificato significa che Kavita non ha scansionato alcuni file. Ciò si verifica su vecchi file esistenti prima della versione 0.7. Potrebbe essere necessario eseguire una scansione forzata tramite la modalità Impostazioni libreria.", + "visualisation-label": "Visualizzazione", + "data-table-label": "Tabella dati", + "extension-header": "Estensione", + "format-header": "Formato", + "total-size-header": "Dimensione Totale", + "total-files-header": "FIle Totali", + "not-classified": "Non Classificato", + "total-file-size-title": "Dimensione totale del file:" }, "reading-activity": { - "title": "", - "legend-label": "", - "x-axis-label": "", - "y-axis-label": "", - "no-data": "", - "time-frame-label": "", - "this-week": "", - "last-7-days": "", - "last-30-days": "", - "last-90-days": "", - "last-year": "", - "all-time": "" + "title": "Attività di Lettura", + "legend-label": "Formati", + "x-axis-label": "Tempo", + "y-axis-label": "Ore Lettura", + "no-data": "Nessun progresso nella lettura", + "time-frame-label": "Lasso di tempo", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" }, "manga-format-stats": { - "title": "", - "visualisation-label": "", - "data-table-label": "", - "format-header": "", - "count-header": "" + "title": "Formato", + "visualisation-label": "Visualizzazione", + "data-table-label": "Tabella Dati", + "format-header": "Formato", + "count-header": "Contare" }, "publication-status-stats": { - "title": "", - "visualisation-label": "", - "data-table-label": "", - "year-header": "", - "count-header": "" + "title": "Stato Pubblicazione", + "visualisation-label": "Visualizzazione", + "data-table-label": "Tabella Dati", + "year-header": "Anno", + "count-header": "Contare" }, "server-stats": { - "total-series-label": "", - "total-series-tooltip": "", - "total-volumes-label": "", - "total-volumes-tooltip": "", - "total-files-label": "", - "total-files-tooltip": "", - "total-size-label": "", - "total-genres-label": "", - "total-genres-tooltip": "", - "total-tags-label": "", - "total-tags-tooltip": "", - "total-people-label": "", - "total-people-tooltip": "", - "total-read-time-label": "", - "total-read-time-tooltip": "", - "series": "", - "reads": "", - "release-years-title": "", - "most-active-users-title": "", - "popular-libraries-title": "", - "popular-series-title": "", - "recently-read-title": "", - "genre-count": "", - "tag-count": "", - "people-count": "", - "tags": "", - "people": "", - "genres": "" + "total-series-label": "Serie Totali", + "total-series-tooltip": "Serie totali: {{count}}", + "total-volumes-label": "Volumi Totali", + "total-volumes-tooltip": "Volumi Totali: {{count}}", + "total-files-label": "File Totali", + "total-files-tooltip": "File Totali: {{count}}", + "total-size-label": "Dimensione totale", + "total-genres-label": "Generi Totali", + "total-genres-tooltip": "Generi Totali: {{count}}", + "total-tags-label": "Tags Totali", + "total-tags-tooltip": "Tags Totali: {{count}}", + "total-people-label": "Persone Totali", + "total-people-tooltip": "Persone Totali: {{count}}", + "total-read-time-label": "Tempo di lettura totale", + "total-read-time-tooltip": "Tempo di lettura totale: {{count}}", + "series": "serie", + "reads": "lettori", + "release-years-title": "Anni di rilascio", + "most-active-users-title": "Utenti più attivi", + "popular-libraries-title": "Biblioteche popolari", + "popular-series-title": "Serie popolari", + "recently-read-title": "Letto di recente", + "genre-count": "{{num}} Generi", + "tag-count": "{{num}} Tags", + "people-count": "{{num}} Persone", + "tags": "Tags", + "people": "Persone", + "genres": "Generi" }, "errors": { - "series-doesnt-exist": "", - "collection-invalid-access": "", - "unknown-crit": "", - "user-not-auth": "", - "error-code": "", - "download": "", - "not-found": "", - "generic": "", - "rejected-cover-upload": "", - "invalid-confirmation-url": "", - "invalid-confirmation-email": "", - "invalid-password-reset-url": "" + "series-doesnt-exist": "Questa serie non esiste più", + "collection-invalid-access": "Non hai accesso ad alcuna libreria a cui appartiene questo tag oppure questa raccolta non è valida", + "unknown-crit": "Si è verificato un errore critico sconosciuto", + "user-not-auth": "L'utente non è autenticato", + "error-code": "{{num}} Errore", + "download": "Si è verificato un problema durante il download di questo file oppure non disponi delle autorizzazioni", + "not-found": "questo URL non esiste", + "generic": "Qualcosa di inaspettato è andato storto", + "rejected-cover-upload": "Impossibile recuperare l'immagine a causa del rifiuto della richiesta da parte del server. Si prega di scaricare e caricare invece dal file.", + "invalid-confirmation-url": "URL di conferma non valido", + "invalid-confirmation-email": "E-mail di conferma non valida", + "invalid-password-reset-url": "URL di reimpostazione della password non valido" }, "toasts": { - "regen-cover": "", - "no-pages": "", - "download-in-progress": "", - "scan-queued": "", - "server-settings-updated": "", - "reset-ip-address": "", - "reset-base-url": "", - "unauthorized-1": "", - "unauthorized-2": "", - "no-updates": "", - "confirm-delete-user": "", - "user-deleted": "", - "email-sent-to-user": "", - "click-email-link": "", - "series-added-to-collection": "", - "no-series-collection-warning": "", - "collection-updated": "", - "reading-list-deleted": "", - "reading-list-updated": "", - "confirm-delete-reading-list": "", - "item-removed": "", - "nothing-to-remove": "", - "series-added-to-reading-list": "", - "volumes-added-to-reading-list": "", - "chapter-added-to-reading-list": "", - "multiple-added-to-reading-list": "", - "select-files-warning": "", - "reading-list-imported": "", - "incognito-off": "", - "email-service-reset": "", - "email-service-reachable": "", - "email-service-unresponsive": "", - "refresh-covers-queued": "", - "library-file-analysis-queued": "", - "entity-read": "", - "entity-unread": "", - "mark-read": "", - "mark-unread": "", - "series-removed-want-to-read": "", - "series-deleted": "", - "file-send-to": "", - "theme-missing": "", - "email-sent": "", - "k+-license-saved": "", - "k+-unlocked": "", - "k+-error": "", - "k+-delete-key": "", - "library-deleted": "", - "copied-to-clipboard": "", - "book-settings-info": "", - "no-next-chapter": "", - "no-prev-chapter": "", - "load-next-chapter": "", - "load-prev-chapter": "", - "account-registration-complete": "", - "account-migration-complete": "", - "password-reset": "", - "password-updated": "", - "forced-scan-queued": "", - "library-created": "", - "anilist-token-updated": "", - "age-restriction-updated": "", - "email-sent-to-no-existing": "", - "email-sent-to": "", - "change-email-private": "", - "device-updated": "", - "device-created": "", - "confirm-regen-covers": "", - "alert-long-running": "", - "confirm-delete-multiple-series": "", - "confirm-delete-series": "", - "alert-bad-theme": "", - "confirm-library-delete": "", - "confirm-library-type-change": "", - "confirm-download-size": "" + "regen-cover": "È stato accodato un lavoro per rigenerare l'immagine di copertina", + "no-pages": "qui non ci sono pagine Kavita non è stata in grado di leggere questo archivio.", + "download-in-progress": "Il download è già in corso. Attendere prego.", + "scan-queued": "Scansione in coda per {{name}}", + "server-settings-updated": "Impostazioni del server aggiornate", + "reset-ip-address": "Reimpostazione degli indirizzi IP", + "reset-base-url": "Reimpostazione dell'URL di base", + "unauthorized-1": "Non sei autorizzato a visualizzare questa pagina.", + "unauthorized-2": "Non autorizzato", + "no-updates": "Nessun aggiornamento disponibile", + "confirm-delete-user": "Sei sicuro di voler eliminare questo utente?", + "user-deleted": "{{utente}} è stato eliminato", + "email-sent-to-user": "Email inviata a {{utente}}", + "click-email-link": "Fai clic su questo collegamento per confermare la tua email. È necessario confermarlo per poter accedere.", + "series-added-to-collection": "Serie aggiunta alla raccolta {{collectionName}}", + "no-series-collection-warning": "Avvertimento! Nessuna serie selezionata, il salvataggio eliminerà la raccolta. Sei sicuro di voler continuare?", + "collection-updated": "Collezione aggiornata", + "reading-list-deleted": "Elenco di lettura eliminato", + "reading-list-updated": "Elenco di lettura aggiornato", + "confirm-delete-reading-list": "Sei sicuro di voler eliminare l'elenco di lettura? Questa operazione non può essere annullata.", + "item-removed": "Articolo rimosso", + "nothing-to-remove": "Niente da rimuovere", + "series-added-to-reading-list": "Serie aggiunta all'elenco di lettura", + "volumes-added-to-reading-list": "Volume aggiunto all'elenco di lettura", + "chapter-added-to-reading-list": "Capitolo aggiunto alla lista di lettura", + "multiple-added-to-reading-list": "Capitoli e volumi aggiunti alla lista di lettura", + "select-files-warning": "È necessario selezionare i file per andare avanti", + "reading-list-imported": "Elenco di lettura importato", + "incognito-off": "La modalità di navigazione in incognito è disattivata. I progressi inizieranno ora a essere monitorati.", + "email-service-reset": "Reimpostazione del servizio e-mail", + "email-service-reachable": "Il servizio e-mail era raggiungibile", + "email-service-unresponsive": "L'URL del servizio di posta non ha risposto.", + "refresh-covers-queued": "Aggiorna le copertine in coda per {{name}}", + "library-file-analysis-queued": "Analisi dei file della libreria in coda per {{name}}", + "entity-read": "{{name}} è ora letto", + "entity-unread": "{{name}} ora non è letto", + "mark-read": "Contrassegnato come letto", + "mark-unread": "Contrassegnato come non letto", + "series-removed-want-to-read": "Serie rimossa dall'elenco Da leggere", + "series-deleted": "Serie cancellata", + "file-send-to": "File inviati via email a {{name}}", + "theme-missing": "Il tema attivo non esiste più. Aggiorna la pagina.", + "email-sent": "Email inviata a {{email}}", + "k+-license-saved": "Chiave di licenza salvata, ma non è valida. Fare clic su Controlla per riconvalidare l'abbonamento. La prima registrazione potrebbe richiedere un minuto per propagarsi.", + "k+-unlocked": "Kavita+ sbloccato!", + "k+-error": "Si è verificato un errore durante l'attivazione della licenza. Per favore riprova.", + "k+-delete-key": "Ciò eliminerà solo la chiave di licenza di Kavita e consentirà la visualizzazione di un collegamento per l'acquisto. Questo non annullerà il tuo abbonamento! Usalo solo se indicato dal supporto!", + "library-deleted": "La libreria {{name}} è stata rimossa", + "copied-to-clipboard": "Copiato negli appunti", + "book-settings-info": "Puoi modificare le impostazioni del libro, salvare tali impostazioni per tutti i libri e visualizzare il sommario dal cassetto.", + "no-next-chapter": "Impossibile trovare la prossima {{entity}}", + "no-prev-chapter": "Impossibile trovare la {{entity}} precedente", + "load-next-chapter": "Prossimo {{entity}} caricato", + "load-prev-chapter": "{{entity}} precedente caricata", + "account-registration-complete": "Registrazione dell'account completata", + "account-migration-complete": "Migrazione dell'account completata", + "password-reset": "Reimpostazione della password", + "password-updated": "La password è stata aggiornata", + "forced-scan-queued": "È stata avviata una scansione forzata per {{name}}", + "library-created": "Libreria creata con successo. È stata avviata una scansione.", + "anilist-token-updated": "Il token AniList è stato aggiornato", + "age-restriction-updated": "Il limite di età è stato aggiornato", + "email-sent-to-no-existing": "È stata inviata un'e-mail a {{email}} per conferma.", + "email-sent-to": "Un'e-mail è stata inviata al tuo vecchio indirizzo e-mail per conferma.", + "change-email-private": "Il server non è accessibile pubblicamente. Chiedi all'amministratore di recuperare il collegamento di conferma dai registri", + "device-updated": "Dispositivo aggiornato", + "device-created": "Dispositivo creato", + "confirm-regen-covers": "Aggiorna copertine imporrà il ricalcolo di tutte le immagini di copertina. Questa è un'operazione pesante. Sei sicuro di non voler eseguire invece una scansione?", + "alert-long-running": "Questo è un processo a lungo termine. Si prega di dargli il tempo di completarsi prima di invocarlo nuovamente.", + "confirm-delete-multiple-series": "Sei sicuro di voler eliminare la serie {{count}}? Non modificherà i file sul disco.", + "confirm-delete-series": "Sei sicuro di voler eliminare questa serie? Non modificherà i file sul disco.", + "alert-bad-theme": "Nel tema sono presenti CSS non validi o non sicuri. Rivolgiti al tuo amministratore per correggere il problema. Per impostazione predefinita è utilizzato il tema scuro.", + "confirm-library-delete": "Sei sicuro di voler eliminare la libreria {{name}}? Non puoi annullare questa azione.", + "confirm-library-type-change": "La modifica del tipo di libreria attiverà una nuova scansione con regole di analisi diverse e potrebbe portare alla ricreazione delle serie e quindi potresti perdere progressi e segnalibri. Dovresti eseguire il backup prima di farlo. Sei sicuro di voler continuare?", + "confirm-download-size": "Il {{entityType}} è {{size}}. Sei sicuro di voler continuare?", + "list-doesnt-exist": "Questo elenco non esiste" }, "actionable": { - "scan-library": "", - "refresh-covers": "", - "analyze-files": "", - "settings": "", - "edit": "", - "mark-as-read": "", - "mark-as-unread": "", - "scan-series": "", - "add-to": "", - "add-to-want-to-read": "", - "remove-from-want-to-read": "", - "remove-from-on-deck": "", - "others": "", - "add-to-reading-list": "", - "add-to-collection": "", - "send-to": "", - "delete": "", - "download": "", - "read-incognito": "", - "details": "", - "view-series": "", - "clear": "", - "import-cbl": "" + "scan-library": "Scansione libreria", + "refresh-covers": "Aggiorna Copertine", + "analyze-files": "Analizza File", + "settings": "Impostazioni", + "edit": "Modifica", + "mark-as-read": "Segna come Letto", + "mark-as-unread": "Segna come non Letto", + "scan-series": "Analizza Serie", + "add-to": "Aggiungi a", + "add-to-want-to-read": "—", + "remove-from-want-to-read": "Rimuovi da Vuoi leggere", + "remove-from-on-deck": "Rimuovi dal Ponte", + "others": "Altri", + "add-to-reading-list": "Aggiungi alla Lista di Lettura", + "add-to-collection": "Aggiungi alla Collezzione", + "send-to": "Invia a", + "delete": "Elimina", + "download": "Scarica", + "read-incognito": "Lettura Incognito", + "details": "Dettagli", + "view-series": "Visualizza Serie", + "clear": "Oulisci", + "import-cbl": "Importa CBL", + "read": "Leggi", + "add-rule-group-or": "Aggiungi gruppo di regole (OR)", + "remove-rule-group": "Rimuovi Gruppo di Regole", + "add-rule-group-and": "Aggiungi gruppo di regole (AND)" }, "preferences": { - "left-to-right": "", - "right-to-left": "", - "horizontal": "", - "vertical": "", - "automatic": "", - "fit-to-height": "", - "fit-to-width": "", - "original": "", - "fit-to-screen": "", - "no-split": "", - "webtoon": "", - "single": "", - "double": "", - "double-manga": "", - "scroll": "", - "1-column": "", - "2-column": "", - "cards": "", - "list": "", - "up-to-down": "" + "left-to-right": "Da sinistra a destra", + "right-to-left": "Da destra a sinistra", + "horizontal": "Orizzontale", + "vertical": "Verticale", + "automatic": "Automatico", + "fit-to-height": "Adatta all'altezza", + "fit-to-width": "Adatta alla larghezza", + "original": "Originale", + "fit-to-screen": "Adatta allo Schermo", + "no-split": "Nessuna divisione", + "webtoon": "Webtoon", + "single": "Singola", + "double": "Doppia", + "double-manga": "Doppia (Manga)", + "scroll": "Scorri", + "1-column": "1 Colonna", + "2-column": "2 Colonne", + "cards": "Carte", + "list": "Lista", + "up-to-down": "Dall'alto verso il basso" }, "validation": { - "required-field": "", - "valid-email": "", - "password-validation": "" + "required-field": "Il campo è richiesto", + "valid-email": "Questa email deve essere valida", + "password-validation": "La password deve avere una lunghezza compresa tra 6 e 32 caratteri" }, "entity-type": { - "volume": "", - "chapter": "", - "series": "", - "bookmark": "", - "logs": "" + "volume": "volume", + "chapter": "capitolo", + "series": "serie", + "bookmark": "segnalibro", + "logs": "logs" }, "common": { - "reset-to-default": "", - "close": "", - "cancel": "", - "create": "", - "save": "", - "reset": "", - "add": "", - "apply": "", - "delete": "", - "edit": "", - "help": "", - "submit": "", - "email": "", - "read": "", - "loading": "", - "username": "", - "password": "", - "promoted": "", - "select-all": "", - "deselect-all": "", - "series-count": "", - "item-count": "", - "book-num": "", - "issue-hash-num": "", - "issue-num": "", - "chapter-num": "", - "volume-num": "" + "reset-to-default": "Riportare alle condizioni originali", + "close": "Chiudi", + "cancel": "Cancella", + "create": "Crea", + "save": "Salva", + "reset": "Ripristina", + "add": "Aggiungi", + "apply": "Applica", + "delete": "Elimina", + "edit": "Modifica", + "help": "Aiuto", + "submit": "Invia", + "email": "Email", + "read": "Leggi", + "loading": "Caricamento…", + "username": "Nome Utente", + "password": "Password", + "promoted": "Promossa", + "select-all": "Seleziona Tutto", + "deselect-all": "Deseleziona Tutto", + "series-count": "{{num}} Serie", + "item-count": "{{num}} elementi", + "book-num": "Libro", + "issue-hash-num": "Problema #", + "issue-num": "Problema", + "chapter-num": "Capitolo", + "volume-num": "Volume" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "Scorri verso l'alto per passare al capitolo precedente", + "continuous-reading-prev-chapter": "Capitolo Precedente", + "continuous-reading-next-chapter-alt": "Scorri verso l'alto per passare al capitolo successivo", + "continuous-reading-next-chapter": "Capitolo Successivo" + }, + "cover-image-size": { + "default": "Default (320x455)", + "medium": "Medio (640x909)", + "large": "Grande (900x1277)", + "xlarge": "Molto grande (1265x1795)" + }, + "metadata-builder": { + "or": "Abbina uno dei seguenti elementi", + "and": "Abbina tutto quanto segue", + "add-rule": "Aggiungi regola", + "remove-rule": "Rimuovi riga" + }, + "filter-field-pipe": { + "read-progress": "Progresso Lettura", + "read-time": "Tempo Lettura", + "release-year": "Anno di pubblicazione", + "series-name": "Nome Serie", + "summary": "Sommario", + "tags": "Tags", + "translators": "Traduttori", + "user-rating": "Voto dell'utente", + "writers": "Scrittori", + "letterer": "Letterista", + "publication-status": "Stato Pubblicazione", + "penciller": "Disegnatore", + "publisher": "Editore", + "age-rating": "Valutazione dell'età", + "characters": "Caratteri", + "collection-tags": "Tag di raccolta", + "colorist": "Colorista", + "cover-artist": "Artista Copertina", + "editor": "Editore", + "formats": "Formati", + "genres": "Generi", + "inker": "Inchiostratore", + "languages": "Lingue", + "libraries": "Librerie", + "path": "Percorso", + "file-path": "Percorso File" + }, + "filter-comparison-pipe": { + "not-equal": "Non uguale", + "ends-with": "Finisce con", + "is-before": "È prima", + "is-after": "È dopo", + "begins-with": "Inizia con", + "contains": "Contiene", + "equal": "Pari", + "greater-than": "Più grande di", + "greater-than-or-equal": "Maggiore o uguale", + "less-than": "Meno di", + "matches": "Partite", + "does-not-contain": "Non contiene", + "less-than-or-equal": "Minore o uguale", + "is-in-last": "È ultimo", + "is-not-in-last": "Non è l'ultimo", + "must-contains": "Deve Contenere" } } diff --git a/UI/Web/src/assets/langs/ja.json b/UI/Web/src/assets/langs/ja.json index 08aee803e7..79fa02002d 100644 --- a/UI/Web/src/assets/langs/ja.json +++ b/UI/Web/src/assets/langs/ja.json @@ -24,7 +24,7 @@ "not-valid-email": "", "cancel": "", "saving": "保存中。。。", - "update": "" + "update": "更新" }, "user-scrobble-history": { "title": "", @@ -36,7 +36,7 @@ "series-header": "シリーズ", "data-header": "", "is-processed-header": "", - "no-data": "", + "no-data": "データなし", "volume-and-chapter-num": "", "rating": "", "not-applicable": "", @@ -1283,12 +1283,6 @@ "dark-theme-alt": "", "close-reader-alt": "" }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" - }, "manga-reader": { "back": "", "save-globally": "", @@ -1326,7 +1320,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", diff --git a/UI/Web/src/assets/langs/ko.json b/UI/Web/src/assets/langs/ko.json index df8767dcb7..193d4338bd 100644 --- a/UI/Web/src/assets/langs/ko.json +++ b/UI/Web/src/assets/langs/ko.json @@ -1,10 +1,10 @@ { "login": { "title": "로그인", - "username": "아이디", - "password": "비밀번호", - "submit": "로그인", - "password-validation": "비밀번호는 길이는{{min}}에서 {{max}}자 사이여야 합니다", + "username": "{{common.username}}", + "password": "{{common.password}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}", "forgot-password": "비밀번호를 잊으셨나요?" }, "dashboard": { @@ -12,16 +12,19 @@ "server-settings-link": "서버 설정", "recently-updated-title": "최근에 업데이트된 시리즈", "recently-added-title": "새롭게 추가된 시리즈", - "not-granted": "라이브러리에 대한 접근 권한이 없습니다." + "not-granted": "라이브러리에 대한 접근 권한이 없습니다.", + "on-deck-title": "계속 읽기" }, "edit-user": { - "edit": "수정", - "username": "사용자", - "required": "필수 항목입니다", - "not-valid-email": "유효한 메일 주소여야 합니다", + "edit": "{{common.edit}}", + "username": "{{common.username}}", + "required": "{{validation.required-field}}", + "not-valid-email": "{{validation.valid-email}}", "update": "업데이트", - "email": "이메일", - "saving": "저장중…" + "email": "{{common.email}}", + "saving": "저장중…", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}" }, "user-scrobble-history": { "filter-label": "필터", @@ -35,22 +38,23 @@ "not-applicable": "해당 사항 없음", "processed": "처리됨", "not-processed": "처리되지 않음", - "description": "여기에 계정과 연결된 모든 scrobble 데이터베이스가 있습니다. 이벤트가 존재하려면 활성 상태가 있어야 합니다\n scrobble 공급자가 구성되었습니다. 처리된 모든 이벤트는 한 달 후에 지워집니다. 만약 처리되지 않은 이벤트가 있다면, 그것은 삭제됩니다\n 이들은 업스트림에서 일치하는 항목을 형성할 수 없습니다. 관리자에게 연락하여 수정하십시오.", + "description": "여기에서 귀하의 계정과 연결된 스크로블 이벤트를 찾을 수 있습니다. 이벤트가 존재하려면 활성 스크로블 공급자가 구성되어 있어야 합니다. 처리된 모든 이벤트는 한 달 후에 지워집니다. 처리되지 않은 이벤트가 있는 경우 이러한 이벤트는 업스트림에서 일치 항목을 형성할 수 없습니다. 수정하려면 관리자에게 문의하세요.", "data-header": "데이터", - "is-processed-header": "처리됨" + "is-processed-header": "처리됨", + "title": "스크로블 이력" }, "scrobble-event-type-pipe": { "chapter-read": "읽기 진행률", "score-updated": "평점 업데이트", "want-to-read-add": "읽고 싶어요: 추가", "want-to-read-remove": "읽고 싶어요: 제거", - "review": "후기 업데이트" + "review": "리뷰 업데이트" }, "spoiler": { "click-to-show": "스포일러, 표시하려면 클릭" }, "review-series-modal": { - "title": "후기 수정", + "title": "리뷰 수정", "tagline-label": "제목", "review-label": "평가", "close": "{{common.close}}", @@ -59,12 +63,1696 @@ "review-card-modal": { "close": "{{common.close}}", "user-review": "{{username}}님의 리뷰", - "external-mod": "(external)", + "external-mod": "(외부)", "go-to-review": "리뷰로 이동" }, "common": { "close": "닫기", "cancel": "취소", - "save": "저장" + "save": "저장", + "create": "생성", + "apply": "적용", + "read": "읽기", + "item-count": "{{num}} 항목", + "issue-num": "이슈", + "reset-to-default": "기본값으로 재설정", + "book-num": "책", + "issue-hash-num": "이슈 #", + "chapter-num": "챕터", + "email": "이메일", + "volume-num": "볼륨", + "add": "추가", + "delete": "삭제", + "edit": "편집", + "help": "도움말", + "submit": "로그인", + "loading": "로딩중…", + "select-all": "모두 선택", + "deselect-all": "모두 선택 취소", + "series-count": "{{num}} 시리즈", + "reset": "초기화", + "username": "아이디", + "password": "비밀번호", + "promoted": "승격" + }, + "series-metadata-detail": { + "characters-title": "캐릭터", + "cover-artists-title": "표지 아티스트", + "tags-title": "태그", + "collections-title": "{{side-nav.collections}}", + "colorists-title": "컬러리스트", + "editors-title": "편집자", + "inkers-title": "잉커", + "reading-lists-title": "{{side-nav.reading-lists}}", + "links-title": "링크", + "genres-title": "장르", + "writers-title": "작가", + "letterers-title": "레터러", + "pencillers-title": "펜슬러", + "publishers-title": "출판사", + "see-less": "간단히 보기", + "translators-title": "역자", + "see-more": "더보기", + "promoted": "{{common.promoted}}" + }, + "library-settings-modal": { + "force-scan-tooltip": "이렇게 하면 라이브러리에서 강제로 스캔하여 새로운 스캔처럼 취급합니다", + "force-scan": "강제 스캔", + "type-label": "유형", + "manage-reading-list-label": "읽기 목록 관리", + "manage-reading-list-tooltip": "Kavita는 ComicInfo.xml/opf 파일에 있는 StoryArc/StoryArcNumber 및 AlternativeSeries/AlternativeCount 태그에서 읽기 목록을 만듭니다", + "allow-scrobbling-label": "스크로블링 허용", + "include-in-dashboard-label": "대시보드에 포함", + "include-in-search-label": "검색에 포함", + "type-tooltip": "라이브러리 유형은 파일 이름이 구문 분석되는 방식과 UI에 챕터(Manga) vs 이슈(Comic)가 표시되는지 여부를 결정합니다. 책은 만화와 같은 방식으로 작동하지만 UI에서 이름이 다릅니다.", + "folder-watching-tooltip": "이 라이브러리를 감시하는 서버 폴더를 재정의합니다. 끄면 폴더 감시가 이 라이브러리에 포함된 폴더에서 실행되지 않습니다. 라이브러리가 폴더를 공유하는 경우 폴더가 계속 실행될 수 있습니다.", + "include-in-recommendation-tooltip": "라이브러리의 시리즈가 권장 페이지에 포함되어야 합니다.", + "include-in-search-tooltip": "시리즈 및 라이브러리에서 파생된 모든 정보(장르, 인물, 파일)가 검색 결과에 포함되어야 합니다.", + "allow-scrobbling-tooltip": "Kavita가 읽기 이벤트를 스크로블해야 하는 경우 구성된 제공자에게 읽고 싶어요 상태, 평점 및 리뷰를 읽어야 합니다. 이는 서버에 활성 Kavita+ 구독이 있는 경우에만 발생합니다.", + "library-name-unique": "라이브러리 이름은 고유해야 합니다", + "folder-description": "라이브러리에 폴더 추가", + "browse": "미디어 폴더 찾아보기", + "naming-conventions-part-3": "팔로우하고 있는지 확인하려면 이 링크를 확인하세요. 그렇지 않으면 파일이 스캔에 표시되지 않습니다.", + "cover-description-extra": "라이브러리 이미지는 크지 않아야 합니다. 32x32픽셀 크기의 작은 파일을 목표로 합니다. Kavita는 크기에 대한 유효성 검사를 수행하지 않습니다.", + "manage-collection-label": "컬렉션 관리", + "manage-collection-tooltip": "Kavita는 ComicInfo.xml/opf 파일에 있는 SeriesGroup 태그에서 컬렉션을 생성합니다", + "folder-watching-label": "폴더 감시", + "include-in-dashboard-tooltip": "라이브러리의 시리즈가 대시보드에 포함되어야 합니다. 이는 계속 읽기, 최근 업데이트, 최근 추가 또는 모든 사용자 정의 추가와 같은 모든 스트림에 영향을 미칩니다.", + "include-in-recommendation-label": "추천에 포함", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "next": "다음", + "save": "{{common.save}}", + "edit-title": "편집 {{name}}", + "add-title": "라이브러리 추가", + "general-tab": "일반", + "folder-tab": "폴더", + "advanced-tab": "고급", + "name-label": "이름", + "cover-tab": "표지", + "close": "{{common.close}}", + "last-scanned-label": "마지막 스캔:", + "help-us-part-1": "팔로우하여 저희를 도와주세요 ", + "help-us-part-2": "가이드", + "help-us-part-3": "미디어 이름 지정 및 구성.", + "naming-conventions-part-1": "카비타는 ", + "naming-conventions-part-2": "폴더 요구 사항.", + "cover-description": "사용자 지정 라이브러리 이미지 아이콘은 선택 사항입니다", + "required-field": "{{validation.required-field}}" + }, + "manage-library": { + "title": "라이브러리", + "add-library": "라이브러리 추가", + "scan-library": "라이브러리 스캔", + "last-scanned-title": "마지막 스캔:", + "shared-folders-title": "공유 폴더:", + "type-title": "유형:", + "delete-library-by-name": "삭제 {{name}}", + "edit-library": "편집", + "edit-library-by-name": "삭제 {{name}}", + "loading": "{{common.loading}}", + "delete-library": "라이브러리 삭제", + "no-data": "라이브러리가 없습니다. 새로 만들어 보세요." + }, + "manage-settings": { + "notice": "공지:", + "cache-size-label": "캐시 크기", + "on-deck-last-chapter-add-tooltip": "계속 읽기를 포함하기 위해 마지막 챕터 이후의 일수가 추가되었습니다.", + "logging-level-label": "로그 레벨", + "logging-level-tooltip": "문제를 식별하는 데 도움이 되도록 디버그를 사용하십시오. 디버그는 디스크 공간을 많이 차지할 수 있습니다.", + "base-url-label": "기본 URL", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "최소 50MB가 있어야 합니다.", + "restart-required": "포트, 기본 URL, 캐시 크기 또는 IP를 변경하려면 Kavita를 수동으로 다시 시작해야 적용됩니다.", + "port-label": "포트", + "allow-stats-tooltip-part-1": "익명의 사용 데이터를 Kavita의 서버로 보냅니다. 여기에는 사용된 특정 기능, 파일 수, OS 버전, Kavita 설치 버전, CPU 및 메모리에 대한 정보가 포함됩니다. 이 정보를 사용하여 기능, 버그 수정 및 성능 튜닝의 우선 순위를 정합니다. 적용하려면 다시 시작해야 합니다. 참조 ", + "base-url-tooltip": "기본 URL(예: yourdomain.com/kavita)에서 Kavita를 호스팅하려는 경우 이것을 사용하십시오. 루트가 아닌 사용자를 사용하는 도커에서는 지원되지 않습니다.", + "ip-address-tooltip": "서버가 수신하는 쉼표로 구분된 IP 주소 목록입니다. 도커에서 실행 중인 경우 이 문제가 해결되었습니다. 적용하려면 다시 시작해야 합니다.", + "port-tooltip": "서버가 수신하는 포트. 도커에서 실행 중인 경우 이 문제가 해결되었습니다. 적용하려면 다시 시작해야 합니다.", + "log-tooltip": "유지 관리할 로그 수입니다. 기본값은 30, 최소값은 1, 최대값은 30입니다.", + "opds-label": "OPDS", + "field-required": "{{validation.field-required}}", + "max-logs-validation": "{{num}}개 이상의 로그를 가질 수 없습니다", + "ip-address-label": "IP 주소", + "log-label": "로그 일수", + "backup-label": "백업 일수", + "backup-tooltip": "유지 관리할 백업 수입니다. 기본값은 30, 최소값은 1, 최대값은 30입니다.", + "cache-size-tooltip": "무거운 API 캐싱에 허용되는 메모리 양입니다. 기본값은 75MB입니다.", + "on-deck-last-chapter-add-label": "계속 읽기 마지막 챕터 추가(일)", + "on-deck-last-progress-label": "계속 읽기 마지막 진행률(일)", + "allow-stats-label": "익명 사용 수집 허용", + "allow-stats-tooltip-part-2": "무엇을 수집합니다.", + "send-data": "데이터 보내기", + "folder-watching-label": "폴더 감시", + "enable-folder-watching": "폴더 감시 활성화", + "opds-tooltip": "OPDS 지원을 통해 모든 사용자는 OPDS를 사용하여 서버에서 콘텐츠를 읽고 다운로드할 수 있습니다.", + "enable-opds": "OPDS 활성화", + "folder-watching-tooltip": "Kavita가 라이브러리 폴더를 모니터링하여 변경 사항을 감지하고 해당 변경 사항에 대한 스캔을 호출하도록 허용합니다. 이렇게 하면 스캔을 수동으로 호출하거나 야간 스캔을 기다리지 않고도 콘텐츠를 업데이트할 수 있습니다.", + "on-deck-last-progress-tooltip": "계속 읽기를 시작하기 전에 마지막 진행 이후 일 수입니다.", + "min-logs-validation": "최소 1개의 로그가 있어야 합니다", + "min-days-validation": "1일 이상이어야 합니다", + "ip-address-validation": "IP 주소는 유효한 IPv4 또는 IPv6 주소만 포함할 수 있습니다", + "min-cache-validation": "50MB여야 합니다.", + "max-backup-validation": "{{num}}개 이상의 백업을 가질 수 없습니다", + "min-backup-validation": "백업이 1개 이상 있어야 합니다", + "base-url-validation": "기본 URL은 /로 시작하고 끝나야 합니다" + }, + "manage-users": { + "resend": "재전송", + "delete-user-alt": "사용자 삭제 {{user}}", + "edit-user-alt": "사용자 편집 {{user}}", + "resend-invite-tooltip": "초대 재전송", + "setup-user-tooltip": "사용자 설정", + "setup-user-alt": "사용자 설정 {{user}}", + "setup": "설정", + "none": "없음", + "never": "없음", + "no-data": "다른 사용자가 없습니다.", + "loading": "{{common.loading}}", + "you-alt": "(당신)", + "pending-title": "보류 중", + "delete-user-tooltip": "사용자 삭제", + "edit-user-tooltip": "편집", + "resend-invite-alt": "초대 재전송 {{user}}", + "roles-title": "권한:", + "last-active-title": "마지막 접속:", + "title": "활성 사용자", + "invite": "초대", + "change-password-tooltip": "비밀번호 변경", + "change-password-alt": "비밀번호 변경 {{user}}", + "online-now-tooltip": "지금 온라인", + "sharing-title": "공유:" + }, + "edit-collection-tags": { + "series-title": "시리즈에 적용", + "title": "편집 {{collectionName}} 컬렉션", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}", + "required-field": "{{validation.required-field}}", + "save": "{{common.save}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "general-tab": "일반", + "cover-image-tab": "표지 이미지", + "series-tab": "시리즈", + "promote-label": "승격", + "name-validation": "이름은 고유해야 합니다", + "promote-tooltip": "승격은 관리 사용자뿐만 아니라 서버 전체에서 태그를 볼 수 있음을 의미합니다. 이 태그가 있는 모든 시리즈에는 여전히 사용자 액세스 제한이 적용됩니다.", + "summary-label": "요약", + "name-label": "이름" + }, + "nav-header": { + "promoted": "(승격)", + "skip-alt": "메인 콘텐츠로 건너뛰기", + "no-data": "검색 결과가 없습니다", + "server-settings": "서버 설정", + "help": "도움말", + "logout": "로그아웃", + "settings": "설정", + "scroll-to-top-alt": "맨위로 스크롤", + "announcements": "공지 사항", + "search-series-alt": "시리즈 검색", + "search-alt": "검색…" + }, + "edit-series-modal": { + "title": "{{seriesName}} 세부정보", + "translator-label": "역자", + "character-label": "캐릭터", + "web-link-description": "여기에서 외부 서비스에 대한 다양한 링크를 추가할 수 있습니다.", + "general-tab": "일반", + "genres-label": "장르", + "tags-label": "태그", + "penciller-label": "펜슬러", + "letterer-label": "레터러", + "age-rating-label": "연령 등급", + "save": "{{common.save}}", + "language-label": "언어", + "publication-status-label": "출판현황", + "localized-name-label": "현지화된 이름", + "summary-label": "요약", + "release-year-label": "출시 연도", + "add-link-alt": "링크 추가", + "remove-link-alt": "링크 제거", + "cover-image-description": "새 표지 이미지를 업로드하고 선택합니다. 저장을 눌러 표지를 업로드하고 재정의합니다.", + "field-locked-alt": "필드가 잠겨 있습니다", + "info-title": "정보", + "library-title": "라이브러리:", + "format-title": "포맷:", + "created-title": "생성됨:", + "last-read-title": "마지막으로 읽음:", + "total-pages-title": "총 페이지 수:", + "last-modified-title": "마지막으로 수정된 날짜:", + "volume-num": "{{common.volume-num}}", + "max-issue-tooltip": "시리즈의 모든 ComicInfo에서 최대 발행량 또는 발행량 필드", + "related-tab": "관련", + "info-tab": "정보", + "collections-label": "컬렉션", + "name-label": "이름", + "sort-name-label": "정렬 이름", + "web-link-label": "웹 링크", + "added-title": "추가:", + "pages-title": "페이지:", + "chapter-title": "챕터:", + "metadata-tab": "메타데이터", + "people-tab": "인물", + "web-links-tab": "웹 링크", + "cover-image-tab": "표지 이미지", + "cover-artist-label": "표지 아티스트", + "writer-label": "작가", + "publisher-label": "출판사", + "inker-label": "잉커", + "editor-label": "편집자", + "colorist-label": "컬러리스트", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "last-added-title": "마지막으로 추가된 항목:", + "last-scanned-title": "마지막 스캔:", + "folder-path-title": "폴더 경로:", + "publication-status-title": "출판 현황:", + "total-items-title": "총 항목:", + "max-items-title": "최대 항목:", + "size-title": "크기:", + "loading": "{{common.loading}}", + "view-files": "파일 보기", + "highest-count-tooltip": "시리즈의 모든 ComicInfo에서 가장 높은 카운트를 찾았습니다" + }, + "sort-field-pipe": { + "last-modified": "마지막 수정", + "release-year": "출시 연도", + "sort-name": "정렬 이름", + "created": "생성됨", + "time-to-read": "완독 예상 시간", + "last-chapter-added": "추가된 항목" + }, + "manga-format-stats": { + "title": "포맷", + "visualisation-label": "시각화", + "count-header": "카운트", + "format-header": "포맷", + "data-table-label": "데이터 테이블" + }, + "publication-status-stats": { + "visualisation-label": "시각화", + "title": "출판현황", + "year-header": "년", + "count-header": "카운트", + "data-table-label": "데이터 테이블" + }, + "server-stats": { + "tag-count": "{{num}} 태그", + "total-series-label": "총 시리즈", + "total-series-tooltip": "총 시리즈: {{count}}", + "total-files-label": "총 파일", + "total-files-tooltip": "총 파일: {{count}}", + "total-size-label": "총 크기", + "total-genres-tooltip": "총 장르: {{count}}", + "total-tags-label": "총 태그", + "total-tags-tooltip": "총 태그: {{count}}", + "total-read-time-label": "총 읽은 시간", + "total-read-time-tooltip": "총 읽은 시간: {{count}}", + "series": "시리즈", + "reads": "읽음", + "popular-libraries-title": "인기 있는 라이브러리", + "genre-count": "{{num}} 장르", + "people-count": "{{num}} 인물", + "tags": "태그", + "total-genres-label": "총 장르", + "release-years-title": "출시 연도", + "recently-read-title": "최근에 읽음", + "most-active-users-title": "가장 활동적인 사용자", + "popular-series-title": "인기 시리즈", + "people": "인물", + "genres": "장르", + "total-volumes-label": "총 볼륨", + "total-people-label": "총 인물", + "total-people-tooltip": "총 인물: {{count}}", + "total-volumes-tooltip": "총 볼륨: {{count}}" + }, + "errors": { + "not-found": "해당 URL이 존재하지 않습니다", + "generic": "예상치 못한 문제가 발생했습니다", + "invalid-confirmation-url": "잘못된 확인 URL", + "invalid-confirmation-email": "잘못된 확인 이메일", + "invalid-password-reset-url": "잘못된 비밀번호 재설정 URL", + "unknown-crit": "알 수 없는 심각한 오류가 발생했습니다", + "error-code": "{{num}} 오류", + "download": "이 파일을 다운로드하는 중에 문제가 발생했거나 권한이 없습니다", + "rejected-cover-upload": "서버가 요청을 거부하여 이미지를 가져올 수 없습니다. 대신 파일에서 다운로드하여 업로드하세요.", + "series-doesnt-exist": "이 시리즈는 더 이상 존재하지 않습니다", + "collection-invalid-access": "이 태그가 속한 라이브러리에 액세스할 수 없거나 이 컬렉션이 유효하지 않습니다", + "user-not-auth": "사용자가 인증되지 않았습니다" + }, + "toasts": { + "no-updates": "사용 가능한 업데이트 없음", + "confirm-delete-user": "이 사용자를 삭제하시겠습니까?", + "click-email-link": "이메일을 확인하려면 이 링크를 클릭하십시오. 확인을 하셔야 로그인이 가능합니다.", + "volumes-added-to-reading-list": "읽기 목록에 추가된 볼륨", + "mark-read": "읽음으로 표시됨", + "file-send-to": "파일이 {{name}}에 이메일로 전송됨", + "device-updated": "장치 업데이트됨", + "change-email-private": "서버는 공개적으로 액세스할 수 없습니다. 로그에서 확인 링크를 가져오도록 관리자에게 요청하십시오", + "no-series-collection-warning": "경고! 시리즈를 선택하지 않았습니다. 저장하면 컬렉션이 삭제됩니다. 계속 하시겠습니까?", + "nothing-to-remove": "제거할 항목 없음", + "series-added-to-reading-list": "읽기 목록에 추가된 시리즈", + "email-service-reachable": "이메일 서비스에 연결했습니다", + "email-service-unresponsive": "이메일 서비스 URL이 응답하지 않습니다.", + "library-file-analysis-queued": "{{name}}에 대해 대기 중인 라이브러리 파일 분석", + "entity-unread": "{{name}}을(를) 읽지 않음", + "theme-missing": "활성 테마가 더 이상 존재하지 않습니다. 페이지를 새로고침하세요.", + "no-prev-chapter": "이전 {{entity}}을(를) 찾을 수 없습니다", + "account-registration-complete": "계정 등록 완료", + "password-reset": "비밀번호 초기화", + "device-created": "장치 생성됨", + "confirm-delete-reading-list": "읽기 목록을 삭제하시겠습니까? 이것은 되돌릴 수 없습니다.", + "library-deleted": "라이브러리 {{name}}이(가) 제거되었습니다", + "account-migration-complete": "계정 마이그레이션 완료", + "password-updated": "비밀번호가 업데이트되었습니다", + "forced-scan-queued": "{{name}}에 대한 강제 스캔이 시작되었습니다", + "library-created": "라이브러리가 성공적으로 생성되었습니다. 스캔이 시작되었습니다.", + "anilist-token-updated": "AniList 토큰이 업데이트되었습니다", + "age-restriction-updated": "연령 제한이 업데이트되었습니다", + "email-sent-to-no-existing": "확인을 위해 {{email}}로 이메일을 보냈습니다.", + "confirm-download-size": "{{entityType}}은 {{size}}입니다. 정말 계속 하시겠습니까?", + "user-deleted": "{{user}} 가 삭제되었습니다", + "series-added-to-collection": "{{collectionName}} 컬렉션에 시리즈가 추가됨", + "reading-list-deleted": "읽기 목록이 삭제되었습니다", + "chapter-added-to-reading-list": "읽기 목록에 챕터 추가됨", + "multiple-added-to-reading-list": "읽기 목록에 추가된 챕터 및 볼륨", + "select-files-warning": "앞으로 이동하려면 파일을 선택해야 합니다", + "collection-updated": "컬렉션 업데이트됨", + "reading-list-updated": "읽기 목록 업데이트됨", + "item-removed": "삭제된 항목", + "entity-read": "{{name}}을(를) 읽음", + "k+-error": "라이선스를 활성화하는 동안 오류가 발생했습니다. 다시 시도해 주세요.", + "confirm-library-type-change": "라이브러리 유형을 변경하면 구문 분석 규칙이 다른 새 스캔이 트리거되고 시리즈가 다시 생성되어 진행률과 북마크가 손실될 수 있습니다. 이 작업을 수행하기 전에 백업해야 합니다. 정말 계속하시겠습니까?", + "refresh-covers-queued": "{{name}}에 대한 새로고침 표지가 대기 중입니다", + "mark-unread": "읽지 않음으로 표시됨", + "series-removed-want-to-read": "읽고 싶어요 목록에서 제거된 시리즈", + "series-deleted": "시리즈 삭제됨", + "k+-license-saved": "라이선스 키가 저장되었지만 유효하지 않습니다. 가입을 재확인하려면 확인을 클릭하십시오. 처음 등록하는 경우 전파하는 데 1분 정도 걸릴 수 있습니다.", + "email-sent": "{{email}}에 이메일 전송", + "k+-unlocked": "Kavita+ 잠금 해제!", + "k+-delete-key": "이렇게 하면 Kavita의 라이선스 키만 삭제되고 구매 링크가 표시됩니다. 이것은 구독을 취소하지 않습니다! 지원팀에서 지시하는 경우에만 사용하세요!", + "copied-to-clipboard": "클립보드에 복사됨", + "book-settings-info": "책 설정을 수정하고 모든 책에 대한 설정을 저장하고 서랍에서 목차를 볼 수 있습니다.", + "no-next-chapter": "다음 {{entity}}을(를) 찾을 수 없습니다", + "load-next-chapter": "다음 {{entity}} 로드됨", + "load-prev-chapter": "이전 {{entity}} 로드됨", + "email-sent-to": "확인을 위해 이전 이메일 주소로 이메일이 전송되었습니다.", + "regen-cover": "표지 이미지를 재생성하기 위한 작업이 대기열에 추가되었습니다", + "download-in-progress": "다운로드가 이미 진행 중입니다. 기다리세요.", + "scan-queued": "대기 중인 스캔 {{name}}", + "server-settings-updated": "서버 설정 업데이트됨", + "reset-ip-address": "IP 주소 재설정", + "reset-base-url": "기본 URL 재설정", + "unauthorized-1": "이 페이지를 볼 권한이 없습니다.", + "unauthorized-2": "권한이 없음", + "no-pages": "페이지가 없습니다. Kavita는 이 아카이브를 읽을 수 없습니다.", + "email-sent-to-user": "{{user}}에 이메일 전송", + "reading-list-imported": "읽기 목록 가져옴", + "incognito-off": "시크릿 모드가 꺼져 있습니다. 이제 진행 상황이 추적되기 시작합니다.", + "email-service-reset": "이메일 서비스 재설정", + "alert-long-running": "이것은 장기 실행 프로세스입니다. 다시 호출하기 전에 완료할 시간을 주십시오.", + "confirm-delete-multiple-series": "정말 {{count}} 시리즈를 삭제하시겠습니까? 디스크의 파일은 수정하지 않습니다.", + "confirm-delete-series": "이 시리즈를 삭제하시겠습니까? 디스크의 파일은 수정하지 않습니다.", + "alert-bad-theme": "테마에 유효하지 않거나 안전하지 않은 CSS가 있습니다. 이를 수정하려면 관리자에게 문의하세요. 어두운 테마로 기본 설정.", + "confirm-library-delete": "정말로 {{name}} 라이브러리를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "confirm-regen-covers": "표지를 새로 고치면 모든 표지 이미지가 다시 계산됩니다. 이것은 무거운 작업입니다. 대신 스캔을 수행하지 않으시겠습니까?", + "list-doesnt-exist": "이 목록은 존재하지 않습니다" + }, + "actionable": { + "mark-as-read": "읽은 상태로 표시", + "mark-as-unread": "읽지 않은 상태로 표시", + "scan-series": "시리즈 스캔", + "scan-library": "라이브러리 스캔", + "analyze-files": "파일 분석", + "settings": "설정", + "edit": "편집", + "add-to-collection": "컬렉션에 추가", + "send-to": "보내기", + "read": "읽기", + "refresh-covers": "표지 새로 고침", + "add-to-want-to-read": "읽고 싶어요에 추가", + "remove-from-want-to-read": "읽고 싶어요에서 제거", + "add-to": "추가", + "add-to-reading-list": "읽기 목록에 추가", + "delete": "삭제", + "download": "다운로드", + "read-incognito": "시크릿 읽기", + "details": "상세", + "view-series": "시리즈 보기", + "clear": "지우기", + "import-cbl": "CBL 가져오기", + "others": "그 외", + "remove-from-on-deck": "계속 읽기에서 제거", + "add-rule-group-and": "규칙 그룹 추가(AND)", + "add-rule-group-or": "규칙 그룹 추가(OR)", + "remove-rule-group": "규칙 그룹 제거" + }, + "preferences": { + "original": "원본", + "no-split": "분할 안 함", + "fit-to-screen": "화면에 맞추기", + "single": "단일", + "double": "더블", + "double-manga": "더블 (Manga)", + "scroll": "스크롤", + "2-column": "2열", + "cards": "카드", + "up-to-down": "위에서 아래", + "list": "리스트", + "1-column": "1열", + "left-to-right": "왼쪽에서 오른쪽", + "right-to-left": "오른쪽에서 왼쪽", + "horizontal": "가로", + "vertical": "세로", + "automatic": "자동", + "fit-to-height": "높이에 맞추기", + "fit-to-width": "너비에 맞추기", + "webtoon": "웹툰" + }, + "time-ago-pipe": { + "just-now": "방금", + "min-ago": "1분 전", + "hour-ago": "1시간 전", + "day-ago": "1일 전", + "month-ago": "1달 전", + "never": "없음", + "mins-ago": "{{value}} 분 전", + "hours-ago": "{{value}} 시간 전", + "days-ago": "{{value}} 일 전", + "months-ago": "{{value}} 달 전", + "year-ago": "1년 전", + "years-ago": "{{value}} 년 전" + }, + "relationship-pipe": { + "doujinshi": "동인지", + "side-story": "외전", + "edition": "에디션", + "other": "그 외", + "prequel": "프리퀄", + "sequel": "속편", + "spin-off": "스핀오프", + "parent": "원작", + "alternative-setting": "대체 설정", + "adaptation": "적응", + "alternative-version": "대체 버전", + "character": "캐릭터", + "contains": "포함" + }, + "publication-status-pipe": { + "ended": "종료", + "ongoing": "진행중", + "hiatus": "중단", + "completed": "완결", + "cancelled": "취소" + }, + "person-role-pipe": { + "character": "캐릭터", + "colorist": "컬러리스트", + "writer": "작가", + "other": "그 외", + "artist": "작가", + "cover-artist": "표지 작가", + "editor": "편집자", + "inker": "잉커", + "letterer": "레터러", + "penciller": "펜슬러", + "publisher": "출판사" + }, + "manga-format-pipe": { + "epub": "EPUB", + "archive": "Archive", + "image": "Image", + "unknown": "알 수 없음", + "pdf": "PDF" + }, + "library-type-pipe": { + "book": "책", + "manga": "만화(Manga)", + "comic": "만화(Comic)" + }, + "age-rating-pipe": { + "adults-only": "19세 미만 구독 불가", + "g": "G", + "unknown": "알 수 없음", + "everyone": "Everyone", + "everyone-10-plus": "Everyone 10+", + "x18-plus": "X18+", + "not-applicable": "해당 없음", + "early-childhood": "Early Childhood", + "pg": "PG", + "r18-plus": "R18+", + "kids-to-adults": "Kids to Adults", + "mature": "Mature", + "ma15-plus": "MA15+", + "mature-17-plus": "Mature 17+", + "rating-pending": "평가 대기 중", + "teen": "Teen" + }, + "reset-password-modal": { + "save": "{{common.save}}", + "error-label": "오류: ", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "title": "{{username}}의 비밀번호 재설정", + "new-password-label": "새 비밀번호" + }, + "changelog": { + "description-continued": "태그, 당신은 나이틀리 릴리스에 있습니다. 주 버전만 사용 가능한 것으로 표시됩니다.", + "description": "{{installed}}가 표시되지 않는 경우", + "installed": "설치됨", + "download": "다운로드", + "published-label": "게시됨: ", + "available": "사용 가능" + }, + "invite-user": { + "invite": "초대", + "cancel": "{{common.cancel}}", + "setup-user-description": "아래 링크를 사용하여 사용자 계정을 설정하거나 복사 버튼을 사용할 수 있습니다. 새 사용자를 등록하기 위해 링크를 사용하기 전에 로그아웃해야 할 수 있습니다. 외부에서 서버에 액세스할 수 있는 경우 이메일이 사용자에게 전송되고 링크를 사용하여 계정 설정을 완료할 수 있습니다.", + "email": "{{common.email}}", + "close": "{{common.close}}", + "required-field": "{{common.required-field}}", + "setup-user-title": "사용자 초대됨", + "invite-url-label": "초대 URL", + "setup-user-account": "사용자 계정 설정", + "setup-user-account-tooltip": "이것을 복사하여 새 탭에 붙여넣으십시오. 로그아웃해야 할 수도 있습니다.", + "title": "사용자 초대", + "inviting": "초대중…", + "description": "서버에 사용자를 초대합니다. 이메일을 입력하면 계정 생성을 위한 이메일을 보내드립니다. 이메일 서비스를 사용하고 싶지 않다면 에 자신의 이메일 서비스를 사용하거나 가짜 이메일을 사용하십시오(사용자 분실은 작동하지 않음). 관계없이 링크가 표시되며 계정을 수동으로 설정하는 데 사용할 수 있습니다." + }, + "book-line-overlay": { + "close": "{{common.close}}", + "bookmark": "북마크", + "bookmark-label": "북마크 이름", + "save": "{{common.save}}", + "required-field": "{{common.required-field}}", + "copy": "복사" + }, + "book-reader": { + "title": "책 설정", + "toc-header": "ToC", + "go-back": "뒤로가기", + "incognito-mode-label": "시크릿 모드", + "loading-book": "책 로딩중…", + "incognito-mode-alt": "시크릿 모드가 켜져 있습니다. 끄려면 전환하세요.", + "go-to-page-prompt": "{{totalPages}} 페이지가 있습니다. 어떤 페이지로 이동하시겠습니까?", + "next": "다음", + "prev-page": "이전 페이지", + "next-page": "다음 페이지", + "page-label": "페이지", + "pagination-header": "섹션", + "prev-chapter": "이전 챕터/볼륨", + "next-chapter": "다음 챕터/볼륨", + "skip-header": "메인 콘텐츠로 건너뛰기", + "virtual-pages": "가상 페이지", + "settings-header": "설정", + "table-of-contents-header": "목차", + "go-to-page": "페이지로 이동", + "go-to-last-page": "마지막 페이지로 이동", + "bookmarks-header": "북마크", + "previous": "이전" + }, + "confirm-email-change": { + "title": "이메일 변경 확인", + "confirm-description": "귀하의 이메일이 확인되었으며 이제 Kavita 내에서 변경되었습니다. 로그인으로 리디렉션됩니다.", + "success": "성공!", + "non-confirm-description": "이메일 업데이트가 확인되는 동안 잠시 기다려 주십시오." + }, + "series-detail": { + "remove-from-want-to-read": "읽고 싶어요에서 제거", + "add-to-want-to-read": "읽고 싶어요에 추가", + "edit-series-alt": "시리즈 정보 편집", + "download-series--tooltip": "시리즈 다운로드", + "downloading-status": "다운로드 중…", + "user-reviews-alt": "사용자 리뷰", + "no-pages": "{{toasts.no-pages}}", + "no-chapters": "이 볼륨에는 챕터가 없습니다. 읽을 수 없습니다.", + "cover-change": "브라우저에서 이미지를 새로 고치는 데 최대 1분이 걸릴 수 있습니다. 그때까지는 일부 페이지에 이전 이미지가 표시될 수 있습니다.", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "incognito": "시크릿 모드", + "page-settings-title": "페이지 설정", + "close": "{{common.close}}", + "layout-mode-option-card": "카드", + "layout-mode-option-list": "리스트", + "continue-from": "계속읽기 {{title}}", + "read": "{{common.read}}", + "continue": "계속 읽기", + "read-options-alt": "읽기 옵션", + "specials-tab": "스페셜", + "related-tab": "관련", + "recommendations-tab": "추천", + "storyline-tab": "줄거리", + "send-to": "{{deviceName}}에 파일을 이메일로 보냈습니다", + "books-tab": "책", + "volumes-tab": "볼륨" + }, + "read-more": { + "read-more": "더 읽기", + "read-less": "그만 읽기" + }, + "update-notification-modal": { + "help": "업데이트 방법", + "download": "다운로드", + "close": "{{common.close}}", + "title": "새로운 업데이트 사용가능!" + }, + "side-nav-companion-bar": { + "close-filter-and-sort": "필터링 및 정렬 닫기", + "filter-and-sort-alt": "정렬 / 필터", + "page-settings-title": "{{series-detail.page-settings-title}}", + "open-filter-and-sort": "필터링 및 정렬 열기" + }, + "reader-settings": { + "vertical": "세로", + "writing-style-label": "{{user-preferences.writing-style-label}}", + "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-tooltip": "이렇게 하면 리더 문서를 클릭하면 메뉴가 숨겨지고 탭하여 페이지 넘김이 켜집니다", + "line-spacing-label": "{{user-preferences.line-height-book-label}}", + "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "horizontal": "가로", + "margin-label": "{{user-preferences.margin-book-label}}", + "reset-to-defaults": "기본값으로 재설정", + "reader-settings-title": "리더 설정", + "right-to-left": "오른쪽에서 왼쪽", + "left-to-right": "왼쪽에서 오른쪽", + "writing-style-tooltip": "텍스트의 방향을 변경합니다. 가로는 왼쪽에서 오른쪽으로, 세로는 위에서 아래로.", + "tap-to-paginate-label": "탭 해서 페이지 넘김", + "tap-to-paginate-tooltip": "화면 가장자리를 클릭하여 페이지를 넘김", + "on": "켜기", + "off": "끄기", + "fullscreen-label": "전체 화면", + "layout-mode-option-2col": "2열", + "color-theme-title": "색상 테마", + "theme-dark": "다크", + "theme-black": "블랙", + "theme-white": "화이트", + "theme-paper": "종이", + "general-settings-title": "일반 설정", + "font-family-label": "{{user-preferences.font-family-label}}", + "font-size-label": "{{user-preferences.font-size-book-label}}", + "fullscreen-tooltip": "리더를 전체 화면 모드로 전환", + "exit": "끄기", + "enter": "켜기", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-tooltip": "스크롤: epub 파일을 미러링합니다(일반적으로 장당 하나의 긴 스크롤 페이지).
    1열: 한 번에 하나의 가상 페이지를 생성합니다.
    2열: 한 번에 두 개의 가상 페이지를 나란히 생성합니다.", + "layout-mode-option-scroll": "스크롤", + "layout-mode-option-1col": "1열" + }, + "bookmarks": { + "delete-success": "북마크가 제거되었습니다", + "confirm-single-delete": "{{seriesName}}에 대한 모든 북마크를 지우시겠습니까? 이것은 되돌릴 수 없습니다.", + "delete-single-success": "{{seriesName}}의 북마크가 제거되었습니다", + "title": "{{side-nav.bookmarks}}", + "series-count": "{{common.series-count}}", + "no-data-2": "보세요.", + "no-data": "북마크가 없습니다. 새로 만들어", + "confirm-delete": "여러 시리즈의 북마크를 모두 지우시겠습니까? 이것은 되돌릴 수 없습니다." + }, + "bulk-operations": { + "deselect-all": "{{common.deselect-all}}", + "title": "일괄 작업", + "items-selected": "{{num}}개 항목 선택됨", + "mark-as-unread": "읽지 않은 상태로 표시", + "mark-as-read": "읽은 상태로 표시" + }, + "card-detail-drawer": { + "general-tab": "일반", + "metadata-tab": "메타데이터", + "info-tab": "정보", + "pages": "페이지:", + "cover-tab": "표지", + "no-summary": "사용 가능한 요약이 없습니다.", + "read": "{{common.read}}", + "unread": "읽지 않음", + "files": "파일", + "added": "추가됨:", + "size": "크기:", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "not-defined": "정의되지 않음", + "writers-title": "{{series-metadata-detail.writers-title}}", + "genres-title": "{{series-metadata-detail.genres-title}}" + }, + "card-item": { + "cannot-read": "읽을 수 없음" + }, + "chapter-metadata-detail": { + "writers-title": "{{series-metadata-detail.writers-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "no-data": "사용 가능한 메타데이터 없음", + "colorists-title": "{{series-metadata-detail.colorists-title}}", + "inkers-title": "{{series-metadata-detail.inkers-title}}", + "pencillers-title": "{{series-metadata-detail.pencillers-title}}", + "cover-artists-title": "{{series-metadata-detail.cover-artists-title}}", + "editors-title": "{{series-metadata-detail.editors-title}}", + "translators-title": "{{series-metadata-detail.translators-title}}", + "letterers-title": "{{series-metadata-detail.letterers-title}}" + }, + "cover-image-chooser": { + "image-num": "이미지 {{num}}", + "applied": "{{theme-manager.applied}}", + "reset-cover-tooltip": "표지 이미지 재설정", + "drag-n-drop": "드래그 앤 드롭", + "upload": "업로드", + "upload-continued": "이미지", + "url-label": "Url", + "load": "불러오기", + "back": "뒤로가기", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}" + }, + "series-info-cards": { + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "language-title": "언어", + "publication-status-tooltip": "출판현황", + "read-time-title": "완독 예상 시간", + "publication-status-title": "출판", + "disabled": "비활성", + "scrobbling-title": "스크로블링", + "scrobbling-tooltip": "스크로블링 상태", + "off": "꺼짐", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}", + "time-left-title": "남은 시간", + "release-date-title": "{{entity-info-cards.release-date-title}}", + "release-year-tooltip": "출시 연도", + "on": "켜짐", + "format-title": "포맷", + "last-read-title": "마지막으로 읽은 시간", + "length-title": "길이" + }, + "bulk-add-to-collection": { + "loading": "{{common.loading}}", + "collection-label": "컬렉션", + "create": "{{common.create}}", + "close": "{{common.close}}", + "filter-label": "필터", + "title": "컬렉션에 추가", + "promoted": "{{common.promoted}}", + "clear": "{{common.clear}}", + "no-data": "아직 생성된 컬렉션이 없습니다" + }, + "entity-title": { + "special": "스페셜", + "issue-num": "이슈 #", + "chapter": "챕터" + }, + "manage-email-settings": { + "reset": "{{common.reset}}", + "title": "이메일 서비스(SMTP)", + "description": "Kavita는 사용자 초대, 비밀번호 재설정 요청 등과 같은 작업을 지원하는 이메일 서비스와 함께 즉시 제공됩니다. 당사 서비스를 통해 전송된 이메일은 즉시 삭제됩니다. {{link}} 서비스를 설정하여 자신의 이메일 서비스를 사용할 수 있습니다. 이메일 서비스의 URL을 설정하고 테스트 버튼을 사용하여 작동하는지 확인하십시오. 언제든지 이러한 설정을 기본값으로 재설정할 수 있습니다. 사용자에게 유효한 이메일 주소를 사용할 필요는 없지만 인증을 위해 이메일을 비활성화할 수 있는 방법은 없습니다. 확인 링크는 항상 로그에 저장되고 UI에 표시됩니다. 공개적으로 연결할 수 있는 URL을 통해 Kavita에 액세스하지 않거나 호스트 이름 기능이 구성되지 않은 경우 등록/확인 이메일이 전송되지 않습니다.", + "host-name-validation": "호스트 이름은 http(s)로 시작하고 /로 끝나지 않아야 합니다", + "reset-to-default": "{{common.reset-to-default}}", + "send-to-warning": "Send to Device가 작동하려면 자체 이메일 서비스를 호스팅해야 합니다.", + "email-url-label": "이메일 서비스 URL", + "host-name-label": "호스트 이름", + "host-name-tooltip": "도메인 이름(역방향 프록시)을 설정하면 이메일 생성 시 항상 이것을 사용합니다.", + "email-url-tooltip": "이메일 서비스의 정규화된 URL을 사용하세요. 종료 슬래시를 포함하지 마십시오.", + "save": "{{common.save}}", + "test": "테스트" + }, + "manage-scrobble-errors": { + "edit-item-alt": "편집 {{seriesName}}", + "created-header": "생성됨", + "edit-header": "편집", + "clear-errors": "오류 지우기", + "series-header": "시리즈", + "comment-header": "코멘트", + "description": "이 표에는 스크로블링 중에 발견된 문제가 포함되어 있습니다. 이 목록은 관리되지 않습니다. 언제든지 지우고 다음 스크로블 업로드가 표시될 때까지 기다릴 수 있습니다. 알 수 없는 시리즈가 있는 경우 시리즈 이름 또는 지역화된 시리즈 이름을 수정하거나 공급자에 대한 웹 링크를 추가하는 것이 가장 좋습니다.", + "filter-label": "필터" + }, + "manage-tasks-settings": { + "library-database-backup-tooltip": "Kavita가 데이터베이스를 백업하는 빈도입니다.", + "adhoc-tasks-title": "임시 작업", + "job-title-header": "작업 이름", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "last-executed-header": "마지막 실행", + "convert-media-task-desc": "모든 kavita 관리 미디어를 대상 인코딩으로 변환하는 장기 실행 작업을 실행합니다. 이것은 느립니다(특히 ARM 장치에서).", + "convert-media-success": "미디어를 대상 인코딩으로 변환이 대기 중입니다", + "bust-cache-task": "버스트 캐시", + "bust-cache-task-success": "Kavita+ 캐시 버스트", + "clean-up-want-to-read-task-success": "읽고 싶어요가 정리되었습니다", + "backup-database-task": "백업 데이터베이스", + "bust-cache-task-desc": "Kavita+ 캐시 파괴 - 잘못된 일치를 디버깅할 때만 사용해야 합니다.", + "description-header": "설명", + "clear-reading-cache-task": "읽기 캐시 지우기", + "clean-up-want-to-read-task-desc": "사용자가 완전히 읽은 출판 현황이 완결인 모든 시리즈를 읽고 싶어요에서 제거합니다. 24시간마다 실행됩니다.", + "clear-reading-cache-task-desc": "읽기 위해 캐시된 파일을 지웁니다. 지난 24시간 이내에 이전에 읽은 파일을 방금 업데이트한 경우에 유용합니다.", + "clear-reading-cache-task-success": "캐시가 지워졌습니다", + "library-scan-label": "라이브러리 스캔", + "library-scan-tooltip": "Kavita가 라이브러리 파일 주변의 메타데이터를 스캔하고 새로 고치는 빈도입니다.", + "cron-header": "Cron", + "convert-media-task": "미디어를 대상 인코딩으로 변환", + "title": "반복 작업", + "library-database-backup-label": "라이브러리 데이터베이스 백업", + "action-header": "행동", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}", + "clean-up-want-to-read-task": "읽고 싶어요 비우기", + "backup-database-task-desc": "데이터베이스, 북마크, 테마, 수동으로 업로드한 표지 및 구성 파일을 백업합니다.", + "backup-database-task-success": "데이터베이스 백업 작업이 대기 중입니다", + "download-logs-task": "로그 다운로드", + "download-logs-task-desc": "모든 로그 파일을 zip으로 컴파일하고 다운로드합니다.", + "analyze-files-task": "파일 분석", + "analyze-files-task-desc": "파일을 분석하여 확장자와 크기를 생성하는 장기 실행 작업을 실행합니다. 이것은 v0.7 릴리스에 대해 한 번만 실행해야 합니다. v0.7 이후를 설치한 경우에는 필요하지 않습니다.", + "analyze-files-task-success": "파일 분석이 대기 중입니다", + "check-for-updates-task": "업데이트 확인", + "check-for-updates-task-desc": "버전보다 앞서 안정적인 릴리스가 있는지 확인하십시오." + }, + "reading-list-item": { + "remove": "{{common.remove}}", + "read": "{{common.read}}" + }, + "reading-list-detail": { + "remove-read": "읽기 제거", + "read": "{{common.read}}", + "incognito-alt": "(시크릿)", + "no-data": "추가된 사항 없음", + "page-settings-title": "페이지 설정", + "order-numbers-label": "주문 번호", + "item-count": "{{common.item-count}}", + "continue": "계속 읽기", + "read-options-alt": "읽기 옵션", + "characters-title": "{{series-metadata-detail.characters-title}}" + }, + "events-widget": { + "update-available": "업데이트 사용 가능", + "title-alt": "활동", + "downloading-item": "다운로드 중 {{item}}", + "more-info": "자세한 내용을 보려면 클릭하십시오", + "dismiss-all": "모두 해제", + "no-data": "아무것도 없습니다", + "close": "{{common.close}}", + "users-online-count": "{{num}} 사용자 온라인", + "active-events-title": "활성 이벤트:" + }, + "shortcuts-modal": { + "close": "{{common.close}}", + "title": "키보드 단축키", + "prev-page": "이전 페이지로 이동", + "next-page": "다음 페이지로 이동", + "go-to": "페이지로 이동 대화 상자 열기", + "bookmark": "현재 페이지 북마크", + "double-click": "더블 클릭", + "close-reader": "리더 닫기", + "toggle-menu": "메뉴 전환" + }, + "grouped-typeahead": { + "files": "파일", + "reading-lists": "읽기 목록", + "collections": "컬렉션", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "chapters": "챕터", + "libraries": "라이브러리", + "people": "인물", + "tags": "태그", + "genres": "장르" + }, + "add-to-list-modal": { + "filter-label": "필터", + "create": "{{common.create}}", + "title": "읽기 목록에 추가", + "close": "{{common.close}}", + "promoted-alt": "승격", + "no-data": "아직 생성된 목록이 없습니다", + "reading-list-label": "읽기 목록", + "loading": "{{common.loading}}" + }, + "edit-reading-list-modal": { + "general-tab": "일반", + "summary-label": "요약", + "starting-title": "시작", + "promote-label": "승격", + "title": "읽기 목록 편집: {{name}}", + "cover-image-tab": "표지 이미지", + "close": "{{common.close}}", + "required-field": "{{validation.required-field}}", + "year-label": "년", + "month-label": "월", + "save": "{common.save}}", + "name-unique-validation": "이름은 고유해야 합니다", + "ending-title": "종료", + "promote-tooltip": "승격은 관리 사용자뿐만 아니라 서버 전체에서 태그를 볼 수 있음을 의미합니다. 이 태그가 있는 모든 시리즈에는 여전히 사용자 액세스 제한이 적용됩니다.", + "year-validation": "1000보다 커야 합니다. 0 또는 공백", + "month-validation": "1에서 12 사이이거나 비어 있어야 합니다" + }, + "import-cbl-modal": { + "close": "{{common.close}}", + "title": "CBL 가져오기", + "validate-no-issue": "문제 없음", + "restart": "재시작", + "next": "다음", + "import-step": "CBL 가져오기", + "validate-cbl-step": "CBL 검증", + "final-import-step": "마지막 단계", + "validate-no-issue-description": "CBL에 문제가 없습니다. 다음을 누르세요.", + "validate-description": "목록에서 수행할 작업이 있는지 확인하기 위해 모든 파일의 유효성이 검사되었습니다. 실패한 목록은 다음 단계로 이동하지 않습니다. CBL 파일을 수정하고 다시 시도하십시오.", + "dry-run-description": "이것은 모의 실행이며 다음을 누르고 가져오기를 수행하면 어떤 일이 발생하는지 보여줍니다. 모든 실패를 가져오지 않습니다.", + "prev": "이전", + "import": "가져오기", + "validate-warning": "가져오기를 방해하는 CBL 문제가 있습니다. 이 문제를 수정한 후 다시 시도하십시오.", + "import-description": "시작하려면 .cbl 파일을 가져옵니다. Kavita는 가져오기 전에 여러 검사를 수행합니다. 일부 단계는 파일 문제로 인해 진행이 차단됩니다.", + "dry-run-step": "모의 실행" + }, + "pdf-reader": { + "loading-message": "로딩 중……PDF가 예상보다 오래 걸릴 수 있습니다", + "incognito-mode": "시크릿 모드", + "close-reader-alt": "리더 닫기", + "light-theme-alt": "밝은 테마", + "dark-theme-alt": "어두운 테마" + }, + "manga-reader": { + "next-chapter-tooltip": "다음 챕터/볼륨", + "first-page-tooltip": "첫 페이지", + "image-splitting-label": "이미지 분할", + "no-next-chapter": "다음 챕터 없음", + "user-preferences-updated": "사용자 기본 설정 업데이트됨", + "fullscreen": "전체 화면", + "swipe-enabled-label": "스와이프 사용", + "settings-tooltip": "설정", + "enable-comic-book-label": "만화책 에뮬레이션", + "image-scaling-label": "이미지 스케일링", + "layout-mode-switched": "이중 레이아웃을 렌더링할 공간이 부족하여 레이아웃 모드가 단일로 전환됨", + "height": "높이", + "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}", + "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "prev-chapter-tooltip": "이전 챕터/볼륨", + "save-globally": "전역 설정", + "prev-page-tooltip": "이전 페이지", + "next-page-tooltip": "다음 페이지", + "collapse": "접기", + "back": "뒤로가기", + "shortcuts-menu-alt": "키보드 단축키 모달", + "incognito-alt": "시크릿 모드가 켜져 있습니다. 끄려면 전환하세요.", + "incognito-title": "시크릿 모드:", + "last-page-tooltip": "마지막 페이지", + "left-to-right-alt": "왼쪽에서 오른쪽", + "right-to-left-alt": "오른쪽에서 왼쪽", + "reading-direction-tooltip": "읽는 방향: ", + "reading-mode-tooltip": "읽기 모드", + "width": "너비", + "original": "원본", + "brightness-label": "밝기", + "first-time-reading-manga": "메뉴를 열려면 언제든지 이미지를 탭하세요. 다른 설정을 구성하거나 진행 표시줄을 클릭하여 페이지로 이동할 수 있습니다. 이미지의 측면을 탭하면 다음/이전 페이지로 이동합니다.", + "no-prev-chapter": "이전 챕터 없음" + }, + "metadata-filter": { + "penciller-label": "펜슬러", + "inker-label": "잉커", + "editor-label": "편집자", + "unread": "읽지 않음", + "age-rating-label": "연령 등급", + "language-label": "언어", + "max": "최대", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "letterer-label": "레터러", + "sort-by-label": "정렬 기준", + "filter-title": "필터", + "format-label": "포맷", + "ascending-alt": "오름차순", + "descending-alt": "내림차순", + "genres-label": "장르", + "cover-artist-label": "표지 아티스트", + "collections-label": "컬렉션", + "writer-label": "작가", + "libraries-label": "라이브러리", + "tags-label": "태그", + "publisher-label": "출판사", + "translator-label": "역자", + "read-progress-label": "읽기 진행률", + "colorist-label": "컬러리스트", + "character-label": "캐릭터", + "read": "읽음", + "in-progress": "진행 중", + "rating-label": "평점", + "publication-status-label": "출판현황", + "series-name-tooltip": "시리즈 이름은 이름, 정렬 이름 또는 현지화된 이름을 기준으로 필터링됩니다", + "series-name-label": "시리즈 이름", + "release-label": "출시", + "min": "최소", + "limit-label": "다음으로 제한" + }, + "day-breakdown": { + "x-axis-label": "요일", + "title": "일 분석", + "y-axis-label": "읽기 이벤트" + }, + "reading-activity": { + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-year": "{{time-periods.last-year}}", + "this-week": "{{time-periods.this-week}}", + "last-90-days": "{{time-periods.last-90-days}}", + "all-time": "{{time-periods.all-time}}", + "no-data": "읽기 진행 없음", + "title": "읽기 활동", + "x-axis-label": "시간", + "y-axis-label": "읽기 시간", + "time-frame-label": "기간", + "legend-label": "포맷" + }, + "validation": { + "required-field": "이 필드는 필수입니다", + "valid-email": "유효한 이메일이어야 합니다", + "password-validation": "비밀번호는 6~32자 사이여야 합니다" + }, + "entity-type": { + "logs": "로그", + "series": "시리즈", + "bookmark": "북마크", + "volume": "볼륨", + "chapter": "챕터" + }, + "user-preferences": { + "account-tab": "계정", + "preferences-tab": "기본 설정", + "3rd-party-clients-tab": "서드파티 클라이언트", + "theme-tab": "테마", + "stats-tab": "통계", + "success-toast": "사용자 기본 설정이 변경되었습니다", + "devices-tab": "장치", + "scrobbling-tab": "스크로블링", + "global-settings-title": "전역 설정", + "page-layout-mode-label": "페이지 레이아웃 모드", + "page-layout-mode-tooltip": "시리즈 세부 정보 페이지에서 항목을 카드 또는 목록 보기로 표시합니다.", + "locale-label": "언어", + "locale-tooltip": "Kavita가 표시하는 언어", + "disable-animations-label": "애니메이션 비활성화", + "disable-animations-tooltip": "사이트에서 애니메이션을 끕니다. 전자책 단말기에 유용합니다.", + "collapse-series-relationships-label": "시리즈 관계 접기", + "collapse-series-relationships-tooltip": "Kavita는 관계가 없거나 상위/속편인 시리즈를 표시해야 합니까", + "share-series-reviews-label": "시리즈 리뷰 공유", + "share-series-reviews-tooltip": "Kavita에 다른 사용자를 위한 시리즈 리뷰를 포함합니까", + "image-reader-settings-title": "이미지 리더", + "reading-direction-label": "읽는 방향", + "reading-direction-tooltip": "다음 페이지로 이동할 때 클릭해야 하는 방향입니다. 오른쪽에서 왼쪽은 화면 왼쪽을 클릭하여 다음 페이지로 이동하는 것을 의미합니다.", + "scaling-option-label": "스케일링 옵션", + "scaling-option-tooltip": "화면에 맞게 이미지 크기를 조정하는 방법.", + "page-splitting-label": "페이지 분할", + "page-splitting-tooltip": "전체 너비 이미지를 분할하는 방법(예: 왼쪽 및 오른쪽 이미지가 모두 결합됨)", + "reading-mode-label": "읽기 모드", + "layout-mode-label": "레이아웃 모드", + "layout-mode-tooltip": "단일 이미지를 화면에 렌더링하거나 두 개의 이미지를 나란히 렌더링합니다", + "background-color-label": "배경색", + "auto-close-menu-label": "자동 메뉴 닫기", + "emulate-comic-book-label": "만화책 에뮬레이션", + "show-screen-hints-label": "화면에 힌트 표시", + "swipe-to-paginate-label": "스와이프하여 페이지 넘김", + "book-reader-settings-title": "북 리더", + "immersive-mode-tooltip": "문서를 클릭하고 탭할 때 페이지 넘기기가 켜져 있으면 메뉴가 숨겨집니다", + "reading-direction-book-tooltip": "다음 페이지로 이동할 때 클릭해야 하는 방향입니다. 오른쪽에서 왼쪽은 화면 왼쪽을 클릭하여 다음 페이지로 이동하는 것을 의미합니다.", + "font-family-label": "글꼴", + "font-family-tooltip": "로드할 글꼴 모음입니다. 기본값은 책의 기본 글꼴을 로드합니다", + "reading-direction-book-label": "읽는 방향", + "title": "사용자 대시보드", + "pref-description": "계정에 연결된 전역 설정입니다.", + "blur-unread-summaries-label": "읽지 않은 요약 흐리게 처리", + "blur-unread-summaries-tooltip": "읽기 진행률이 없는 볼륨 또는 챕터의 요약 텍스트를 흐리게 표시(스포일러 방지)", + "prompt-on-download-label": "다운로드시 묻기", + "prompt-on-download-tooltip": "다운로드 크기가 {{size}}MB를 초과하면 메시지 표시", + "immersive-mode-label": "몰입 모드", + "tap-to-paginate-tooltip": "북 리더 화면의 측면을 탭하여 이전/다음 페이지로 이동할 수 있습니다", + "tap-to-paginate-label": "탭하여 페이지 넘김", + "writing-style-label": "작문 스타일", + "color-theme-book-label": "색상 테마", + "writing-style-tooltip": "텍스트의 방향을 변경합니다. 가로는 왼쪽에서 오른쪽으로, 세로는 위에서 아래로.", + "layout-mode-book-label": "레이아웃 모드", + "layout-mode-book-tooltip": "콘텐츠를 어떻게 배치할지 정합니다 스크롤은 책에 포함된 대로입니다. 1 또는 2 열은 장치의 높이에 맞게 페이지당 1 또는 2 열의 텍스트가 들어갑니다", + "color-theme-book-tooltip": "북 리더 콘텐츠 및 메뉴에 적용할 색상 테마", + "font-size-book-label": "글꼴 크기", + "line-height-book-label": "줄 간격", + "line-height-book-tooltip": "줄 사이의 간격", + "margin-book-label": "여백", + "margin-book-tooltip": "화면 양쪽의 간격입니다. 이 설정과 상관없이 모바일 장치에서는 0으로 재정의됩니다.", + "clients-opds-alert": "이 서버에서 OPDS를 사용할 수 없습니다. 이것은 Tachiyomi 사용자에게 영향을 미치지 않습니다.", + "clients-opds-description": "모든 서드파티 클라이언트는 API 키 또는 아래의 연결 URL을 사용합니다. 이것은 암호와 같으므로 비공개로 유지하십시오.", + "clients-api-key-tooltip": "API 키는 암호와 같습니다. 비밀로 유지하고 안전하게 유지하십시오.", + "clients-opds-url-tooltip": "OPDS URL", + "reset": "{{common.reset}}", + "save": "{{common.save}}" + }, + "download-indicator": { + "progress": "{{percentage}}% 다운로드 됨" + }, + "manage-media-settings": { + "encode-as-description-part-1": "WebP/AVIF는 파일의 공간 요구 사항을 크게 줄일 수 있습니다. WebP/AVIF는 모든 브라우저 또는 버전에서 지원되지 않습니다. 이러한 설정이 귀하의 설정에 적합한지 알아보려면 다음을 방문하십시오. ", + "encode-as-description-part-2": "WebP를 사용할 수 있습니까?", + "encode-as-description-part-3": "AVIF를 사용할 수 있습니까?", + "encode-as-warning": "WebP/AVIF로 이동한 후에는 PNG로 다시 변환할 수 없습니다. 모든 표지를 재생성하려면 라이브러리의 표지를 새로 고쳐야 합니다. 북마크와 파비콘은 변환할 수 없습니다.", + "media-warning": "작업 탭에서 미디어 변환 작업을 트리거해야 합니다.,", + "encode-as-label": "미디어를 다른 이름으로 저장", + "encode-as-tooltip": "Kavita가 관리하는 모든 미디어(표지, 북마크, 파비콘)는 이 유형으로 인코딩됩니다.", + "bookmark-dir-label": "북마크 디렉토리", + "bookmark-dir-tooltip": "북마크가 저장될 위치입니다. 북마크는 소스 파일이며 크기가 클 수 있습니다. 적절한 보관 장소를 선택하십시오. 디렉토리가 관리됩니다. 디렉토리 내의 다른 파일은 삭제됩니다. 도커의 경우 추가 볼륨을 마운트하여 사용하십시오.", + "change": "변경", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "media-issue-title": "미디어 문제", + "scrobble-issue-title": "스크로블 문제", + "cover-image-size-label": "커버 이미지 크기", + "cover-image-size-tooltip": "표지 이미지가 생성되어야 하는 크기입니다. 참고: 기본값보다 크면 페이지 로드 시간이 길어집니다." + }, + "theme-manager": { + "updated-toastr": "사이트 기본값이 {{name}}(으)로 업데이트되었습니다", + "title": "테마 관리자", + "looking-for-theme": "밝은 테마 또는 전자 잉크 테마를 찾고 계십니까? 우리에게는 당신이 사용할 수 있는 몇 가지 사용자 정의 테마가 있습니다 ", + "looking-for-theme-continued": "테마 github.", + "scan": "스캔", + "site-themes": "사이트 테마", + "set-default": "기본값으로 설정", + "apply": "{{common.apply}}", + "applied": "적용됨", + "scan-queued": "사이트 테마 스캔을 대기 중입니다" + }, + "change-email": { + "invite-url-tooltip": "복사하여 새 탭에 붙여넣기", + "permission-error": "이메일을 변경할 수 있는 권한이 없습니다. 서버 관리자에게 문의하십시오.", + "email-label": "{{common.email}}", + "current-password-label": "현재 비밀번호", + "email-not-confirmed": "이 이메일은 확인되지 않았습니다", + "email-updated-title": "이메일 업데이트됨", + "setup-user-account": "사용자 계정 설정", + "invite-url-label": "초대 URL", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "email-updated-description": "아래 링크를 사용하여 계정의 이메일을 확인할 수 있습니다. 서버가 외부에서 액세스할 수 있는 경우 이메일이 전송되고 링크를 사용하여 이메일을 확인할 수 있습니다." + }, + "scrobbling-providers": { + "requires": "이 기능에는 활성 {{product}} 라이선스가 필요합니다", + "title": "스크로블링 공급자", + "token-expired": "만료된 토큰", + "token-input-label": "{{service}} 토큰", + "token-set": "토큰 설정", + "no-token-set": "토큰 설정 없음", + "instructions": "처음 사용자는 Kavita+가 {{service}}와 대화할 수 있도록 아래의 \"{{scrobbling-providers.generate}}\"를 클릭해야 합니다. 프로그램에 권한을 부여한 후 아래 입력란에 토큰을 복사하여 붙여넣으십시오. 언제든지 토큰을 다시 생성할 수 있습니다.", + "generate": "생성", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "top-readers": { + "this-week": "{{time-periods.this-week}}", + "time-selection-label": "기간", + "comics-label": "만화(Comics): {{value}} 시간", + "manga-label": "만화(Manga): {{value}} 시간", + "books-label": "책: {{value}} 시간", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}", + "title": "최고 독자" + }, + "directory-picker": { + "title": "디렉토리 선택", + "path-label": "경로", + "close": "{{common.close}}", + "path-placeholder": "입력 시작 또는 경로 선택", + "instructions": "이동 경로를 보려면 폴더를 선택하십시오. 디렉토리가 보이지 않습니까? /를 먼저 확인해보세요.", + "type-header": "유형", + "name-header": "이름", + "cancel": "{{common.cancel}}", + "share": "공유", + "help": "{{common.help}}" + }, + "library-access-modal": { + "no-data": "아직 라이브러리 설정이 없습니다.", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "라이브러리 액세스", + "close": "{{common.close}}", + "reset": "{{common.reset}}" + }, + "reset-password": { + "title": "비밀번호 초기화", + "description": "계정의 이메일을 입력하세요. Kavita는 파일에 유효한 이메일이 있는 경우 이메일을 보내드립니다. 그렇지 않으면 관리자에게 로그의 링크를 요청하십시오.", + "email-label": "{{common.email}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "submit": "{{common.submit}}" + }, + "license": { + "loading": "{{common.loading}}", + "activate-description": "Stripe에 등록하는 데 사용된 라이선스 키와 이메일을 입력하세요", + "activate-license-label": "라이선스 키", + "activate-save": "{{common.save}}", + "invalid-license-tooltip": "구독이 종료된 경우 새 구독을 생성하려면 지원팀에 이메일을 보내야 합니다", + "title": "Kavita+ 라이선스", + "manage": "관리", + "check": "확인", + "activate": "활성화", + "renew": "갱신", + "cancel": "{{common.cancel}}", + "edit": "{{common.edit}}", + "buy": "구입", + "license-not-valid": "라이선스가 유효하지 않음", + "no-license-key": "라이센스 키 없음", + "license-valid": "라이센스가 유효합니다", + "activate-email-label": "{{common.email}}", + "activate-delete": "삭제" + }, + "confirm-email": { + "password-validation": "{{validation.password-validation}}", + "register": "등록", + "title": "등록", + "description": "양식을 작성하여 등록을 완료하십시오", + "error-label": "오류: ", + "username-label": "{{common.username}}", + "password-label": "{{common.password}}", + "email-label": "{{common.email}}", + "required-field": "{{common.required-field}}", + "valid-email": "{{common.valid-email}}" + }, + "register": { + "username-label": "{{common.username}}", + "title": "등록", + "description": "양식을 작성하여 관리자 계정 등록", + "email-label": "{{common.email}}", + "email-tooltip": "이메일은 실제 주소일 필요는 없지만 잊어버린 비밀번호에 대한 액세스를 제공합니다. 맞춤 이메일 서비스 호스트 없이 암호를 잊은 경우를 제외하고는 서버 외부로 전송되지 않습니다.", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "등록" + }, + "user-holds": { + "description": "이것은 업스트림 공급자에게 스크로블되지 않는 시리즈의 사용자 관리 목록입니다. 언제든지 시리즈를 제거할 수 있으며 다음 스크로블 가능 이벤트(읽기 진행률, 평점, 읽고 싶어요 상태)가 이벤트를 트리거합니다.", + "title": "스크러블 홀드" + }, + "entity-info-cards": { + "read-time-title": "{{series-info-cards.read-time-title}}", + "pages-count": "{{num}} 페이지", + "words-count": "{{num}} 단어", + "reading-time-title": "완독 예상 시간", + "hours": "시간", + "release-date-tooltip": "출시일", + "age-rating-title": "연령 등급", + "length-title": "길이", + "tags-title": "{{series-metadata-detail.tags-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "release-date-title": "출시", + "links-title": "{{series-metadata-detail.links-title}}", + "isbn-title": "ISBN", + "date-added-title": "추가된 날짜", + "last-read-title": "마지막으로 읽은 시간", + "less-than-hour": "<1 시간", + "range-hours": "{{value}} {{hourWord}}", + "hour": "시간", + "size-title": "크기", + "id-title": "아이디" + }, + "manage-alerts": { + "extension-header": "확장자", + "clear-alerts": "알림 지우기", + "filter-label": "필터", + "description-part-1": "이 표에는 미디어를 스캔하거나 읽는 동안 발견된 문제가 포함되어 있습니다. 이 목록은 관리되지 않습니다. 언제든지 지우고 라이브러리(강제) 스캔을 사용하여 분석을 수행할 수 있습니다. 몇 가지 일반적인 오류 목록과 그 의미는 다음에서 찾을 수 있습니다. ", + "description-part-2": "위키.", + "details-header": "상세", + "file-header": "파일", + "comment-header": "코멘트" + }, + "default-date-pipe": { + "never": "없음" + }, + "theme": { + "theme-dark": "다크", + "theme-black": "블랙", + "theme-paper": "종이", + "theme-white": "화이트" + }, + "restriction-selector": { + "not-applicable-for-admins": "관리자에게는 적용되지 않습니다.", + "description": "선택하면 선택한 제한보다 큰 항목이 하나 이상 있는 모든 시리즈 및 읽기 목록이 결과에서 제거됩니다.", + "title": "연령 등급 제한", + "age-rating-label": "연령 등급", + "no-restriction": "제한 없음", + "include-unknowns-label": "알 수 없는 항목 포함", + "include-unknowns-tooltip": "켤 경우 연령 제한이 있는 Unknown이 허용됩니다. 이로 인해 연령 제한이 있는 사용자에게 태그가 지정되지 않은 미디어가 유출될 수 있습니다." + }, + "edit-device": { + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "device-name-label": "장치 이름", + "email-label": "{{common.email}}", + "email-tooltip": "이 이메일은 보내기를 통해 파일을 수락하는 데 사용됩니다", + "device-platform-label": "장치 플랫폼" + }, + "change-password": { + "current-password-label": "현재 비밀번호", + "new-password-label": "새 비밀번호", + "confirm-password-label": "비밀번호 확인", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "비밀번호가 일치해야합니다", + "permission-error": "비밀번호를 변경할 수 있는 권한이 없습니다. 서버 관리자에게 문의하십시오.", + "password-label": "{{common.password}}" + }, + "api-key": { + "copy": "복사", + "regen-warning": "API 키를 재생성하면 기존 클라이언트가 무효화됩니다.", + "no-key": "오류 - 키가 설정되지 않음", + "confirm-reset": "이렇게 하면 설정한 모든 OPDS 구성이 무효화됩니다. 정말 계속 하시겠습니까?", + "key-reset": "API 키 재설정", + "show": "보기" + }, + "typeahead": { + "add-custom-item": ", 사용자 정의 목록을 추가하려면 입력하세요", + "locked-field": "필드가 잠겨 있습니다", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "no-data": "데이터 없음", + "add-item": "추가 {{item}}…" + }, + "generic-list-modal": { + "close": "{{common.close}}", + "clear": "지우기", + "filter": "필터", + "open-filtered-search": "{{item}}에 대한 필터링된 검색 열기" + }, + "user-stats-info-cards": { + "avg-reading-per-week-label": "평균 읽기 시간/주", + "total-pages-read-label": "총 읽은 페이지", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", + "total-words-read-label": "총 읽은 단어", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "읽은 챕터", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "독서 시간", + "chapters": "{{value}} 챕터", + "last-active-label": "마지막 접속" + }, + "role-selector": { + "title": "권한" + }, + "time-periods": { + "this-week": "이번 주", + "last-7-days": "지난 7일", + "last-30-days": "지난 30일", + "last-90-days": "지난 90일", + "last-year": "작년", + "all-time": "항상" + }, + "device-platform-pipe": { + "custom": "커스텀" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "계정이 목록의 모든 시리즈에 액세스할 수 없거나 Kavita가 목록에 없는 시리즈입니다.", + "chapter-missing": "{{series}}: 챕터 {{chapter}} 가 Kavita에서 누락되었습니다. 이 항목은 건너뜁니다.", + "empty-file": "cbl 파일이 비어 있으며 수행할 작업이 없습니다.", + "name-conflict": "cbl 파일과 일치하는 읽기 목록({{readingListName}})이 계정에 이미 존재합니다.", + "series-collision": "{{seriesLink}} 시리즈가 다른 라이브러리에 있는 동일한 이름의 다른 시리즈와 충돌합니다.", + "series-missing": "{{series}} 시리즈가 Kavita에서 누락되었거나 귀하의 계정에 권한이 없습니다. 이 시리즈의 모든 항목은 가져오기에서 건너뜁니다.", + "volume-missing": "{{series}}: Kavita에서 {{volume}} 볼륨이 누락되었거나 계정에 권한이 없습니다. 이 볼륨 번호가 있는 모든 항목을 건너뜁니다.", + "all-chapter-missing": "모든 챕터가 Kavita의 챕터와 일치하는 것은 아닙니다.", + "invalid-file": "파일이 손상되었거나 예상 태그/사양과 일치하지 않습니다.", + "success": "{{series}} 볼륨 {{volume}} 챕터 {{chapter}}가 성공적으로 매핑되었습니다." + }, + "time-duration-pipe": { + "minutes": "{{value}} 분", + "days": "{{value}} 일", + "months": "{{value}} 달", + "years": "{{value}} 년", + "hours": "{{value}} 시간" + }, + "library-selector": { + "title": "라이브러리", + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "no-data": "아직 라이브러리 설정이 없습니다." + }, + "badge-expander": { + "more-items": "와 {{count}} 더" + }, + "side-nav": { + "home": "홈", + "collections": "컬렉션", + "want-to-read": "읽고 싶어요", + "reading-lists": "읽기 목록", + "filter-label": "필터", + "all-series": "모든 시리즈", + "bookmarks": "북마크", + "clear": "지우기", + "donate": "기부" + }, + "card-detail-layout": { + "total-items": "{{count}} 총 항목" + }, + "edit-series-relation": { + "description-part-2": "힌트는 위키.", + "description-part-1": "어떤 관계를 추가해야 할지 잘 모르시겠습니까? 우리를 참조하십시오", + "target-series": "대상 시리즈", + "relationship": "관계", + "remove": "제거", + "add-relationship": "관계 추가", + "parent": "{{relationship-pipe.parent}}" + }, + "external-series-card": { + "open-external": "외부 열기" + }, + "list-item": { + "read": "{{common.read}}" + }, + "manage-system": { + "feature-request-title": "기능 요청", + "title": "시스템 정보", + "version-title": "버전", + "installId-title": "인스톨 아이디", + "more-info-title": "더 많은 정보", + "home-page-title": "홈페이지:", + "wiki-title": "위키:", + "discord-title": "디스코드:", + "donations-title": "기부:", + "source-title": "소스:" + }, + "library-detail": { + "library-tab": "라이브러리", + "recommended-tab": "추천" + }, + "admin-dashboard": { + "title": "관리자 대시보드", + "general-tab": "일반", + "users-tab": "사용자", + "libraries-tab": "라이브러리", + "media-tab": "미디어", + "logs-tab": "로그", + "email-tab": "이메일", + "tasks-tab": "작업", + "statistics-tab": "통계", + "system-tab": "시스템", + "kavita+-tab": "Kavita+", + "kavita+-desc-part-1": "Kavita+는 이 Kavita 인스턴스의 모든 사용자를 위한 기능을 잠금 해제하는 프리미엄 구독 서비스입니다. 잠금 해제하려면 구독 구매 ", + "kavita+-desc-part-2": "프리미엄 혜택", + "kavita+-desc-part-3": "오늘!" + }, + "reading-lists": { + "no-data": "읽기 목록이 없습니다.", + "create-one-part-1": "새로 만들어", + "create-one-part-2": "보세요", + "title": "읽기 목록", + "item-count": "{{common.item-count}}" + }, + "file-breakdown-stats": { + "visualisation-label": "시각화", + "format-tooltip": "분류되지 않음은 Kavita가 일부 파일을 검사하지 않았음을 의미합니다. 이것은 v0.7 이전에 존재하는 오래된 파일에서 발생합니다. 라이브러리 설정 모달을 통해 강제 스캔을 실행해야 할 수도 있습니다.", + "format-title": "포맷", + "format-header": "포맷", + "total-files-header": "총 파일", + "data-table-label": "데이터 테이블", + "extension-header": "확장자", + "total-size-header": "총 크기", + "total-file-size-title": "총 파일 크기:", + "not-classified": "분류되지 않음" + }, + "want-to-read": { + "series-count": "{{common.series-count}}", + "no-items": "항목이 없습니다. 시리즈를 추가해 보세요.", + "no-items-filtered": "현재 필터와 일치하는 항목이 없습니다.", + "title": "읽고 싶어요" + }, + "review-card": { + "your-review": "당신의 리뷰", + "external-review": "외부 리뷰", + "local-review": "리뷰", + "rating-percentage": "평점 {{r}}%" + }, + "site-theme-provider-pipe": { + "system": "시스템", + "user": "사용자" + }, + "manage-devices": { + "title": "장치 관리자", + "description": "이 섹션은 웹 브라우저를 통해 Kavita에 연결할 수 없고 대신 파일을 허용하는 이메일 주소가 있는 장치를 설정하기 위한 것입니다.", + "devices-title": "장치", + "no-devices": "아직 설정된 장치가 없습니다", + "platform-label": "플랫폼: ", + "email-label": "이메일: ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" + }, + "change-age-restriction": { + "age-restriction-label": "연령 제한", + "unknowns": "알 수 없음", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}" + }, + "user-stats": { + "library-read-progress-title": "라이브러리 읽기 진행률", + "read-percentage": "% 읽음" + }, + "day-of-week-pipe": { + "monday": "월요일", + "tuesday": "화요일", + "wednesday": "수요일", + "thursday": "목요일", + "friday": "금요일", + "saturday": "토요일", + "sunday": "일요일" + }, + "cbl-import-result-pipe": { + "success": "성공", + "partial": "일부", + "failure": "실패" + }, + "all-series": { + "series-count": "{{common.series-count}}", + "title": "모든 시리즈" + }, + "announcements": { + "title": "공지 사항" + }, + "personal-table-of-contents": { + "no-data": "아직 북마크된 항목 없음", + "page": "페이지 {{value}}", + "delete": "삭제 {{bookmarkName}}" + }, + "confirm-reset-password": { + "title": "비밀번호 초기화", + "description": "새 비밀번호를 입력하세요", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}" + }, + "table-of-contents": { + "no-data": "이 책에는 메타데이터 또는 toc 파일에 설정된 목차가 없습니다" + }, + "library-recommended": { + "no-data": "여기에 표시할 내용이 없습니다. 라이브러리에 일부 메타데이터를 추가하거나 무언가를 읽거나 평가하십시오. 이 라이브러리에는 권장 사항이 꺼져 있을 수도 있습니다.", + "more-in-genre": "더 보기 {{genre}}", + "highly-rated": "높은 평가", + "quick-catchups": "빠른 캐치업", + "quick-reads": "빠른 읽기", + "on-deck": "{{dashboard.on-deck-title}}", + "rediscover": "재발견" + }, + "collection-detail": { + "no-data": "항목이 없습니다. 시리즈를 추가해 보세요.", + "no-data-filtered": "현재 필터와 일치하는 항목이 없습니다.", + "title-alt": "Kavita - {{collectionName}} 컬렉션" + }, + "all-collections": { + "title": "컬렉션", + "item-count": "{{common.item-count}}", + "no-data": "컬렉션이 없습니다.", + "create-one-part-1": "새로 만들어", + "create-one-part-2": "보세요" + }, + "carousel-reel": { + "prev-items": "이전 항목", + "next-items": "다음 항목" + }, + "draggable-ordered-list": { + "instructions-alt": "재정렬 입력에 숫자를 입력하면 해당 위치에 항목이 삽입되고 다른 모든 항목의 순서가 업데이트됩니다.", + "reorder-label": "재 주문", + "remove-item-alt": "항목 제거" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "위로 스크롤하여 이전 챕터로 이동", + "continuous-reading-next-chapter-alt": "위로 스크롤하여 다음 챕터로 이동", + "continuous-reading-prev-chapter": "이전 챕터", + "continuous-reading-next-chapter": "다음 챕터" + }, + "filter-field-pipe": { + "collection-tags": "컬렉션 태그", + "colorist": "컬러리스트", + "cover-artist": "커버 아티스트", + "editor": "편집자", + "summary": "요약", + "tags": "태그", + "translators": "역자", + "age-rating": "연령 등급", + "characters": "캐릭터", + "formats": "포맷", + "letterer": "레터러", + "publication-status": "출판현황", + "penciller": "펜슬러", + "publisher": "출판사", + "read-progress": "읽기 진행률", + "genres": "장르", + "read-time": "완독 예상 시간", + "inker": "잉커", + "release-year": "출시 연도", + "user-rating": "사용자 평점", + "writers": "작가", + "languages": "언어", + "libraries": "라이브러리", + "series-name": "시리즈 이름", + "path": "경로", + "file-path": "파일 경로" + }, + "filter-comparison-pipe": { + "is-not-in-last": "마지막에 없음", + "begins-with": "시작", + "contains": "포함", + "equal": "같음", + "less-than": "작음", + "greater-than-or-equal": "크거나 같음", + "matches": "일치", + "less-than-or-equal": "작거나 같음", + "greater-than": "큼", + "does-not-contain": "포함 안 함", + "not-equal": "같지 않음", + "ends-with": "로 끝남", + "is-before": "이전", + "is-after": "이후", + "is-in-last": "마지막에 있음", + "must-contains": "반드시 포함" + }, + "metadata-builder": { + "or": "다음 중 하나와 일치", + "add-rule": "규칙 추가", + "and": "다음 모두 일치", + "remove-rule": "행 {{num}} 제거" + }, + "cover-image-size": { + "default": "기본 (320x455)", + "medium": "중간 (640x909)", + "xlarge": "매우 크게 (1265x1795)", + "large": "크게 (900x1277)" } } diff --git a/UI/Web/src/assets/langs/ms.json b/UI/Web/src/assets/langs/ms.json index cc54d35b30..e2ac58c059 100644 --- a/UI/Web/src/assets/langs/ms.json +++ b/UI/Web/src/assets/langs/ms.json @@ -1,23 +1,23 @@ { "login": { - "title": "", - "username": "", - "password": "", - "password-validation": "", - "forgot-password": "", - "submit": "" + "title": "Log masuk", + "username": "{{biasa.nama_pengguna}}", + "password": "{{biasa.kata_laluan}}", + "password-validation": "{{pengesahan.pengesahan-kata laluan}}", + "forgot-password": "Lupa Kata Laluan?", + "submit": "{{biasa.hantar}}" }, "dashboard": { - "no-libraries": "", - "server-settings-link": "", - "not-granted": "", - "on-deck-title": "", - "recently-updated-title": "", - "recently-added-title": "" + "no-libraries": "Perpustakaan belum di persediakan lagi. Semak pada konfigurasi", + "server-settings-link": "Tetapan Server", + "not-granted": "Anda belum di berikan akses kepada mana-mana perpustakaan.", + "on-deck-title": "Atas Dek", + "recently-updated-title": "Siri Yang Baru-baru ini Di kemaskini", + "recently-added-title": "Siri Baru Di tambah" }, "edit-user": { - "edit": "", - "close": "", + "edit": "{{biasa.sunting}}", + "close": "{{biasa.tutup}}", "username": "", "required": "", "email": "", @@ -1283,12 +1283,6 @@ "dark-theme-alt": "", "close-reader-alt": "" }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" - }, "manga-reader": { "back": "", "save-globally": "", @@ -1326,7 +1320,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", diff --git a/UI/Web/src/assets/langs/nl.json b/UI/Web/src/assets/langs/nl.json index 6d7bd60d93..8295bda57f 100644 --- a/UI/Web/src/assets/langs/nl.json +++ b/UI/Web/src/assets/langs/nl.json @@ -28,7 +28,7 @@ }, "user-scrobble-history": { "title": "Scrobble geschiedenis", - "description": "Hier vindt u alle scrobble-evenementen die aan uw account zijn gekoppeld. Om evenementen te laten bestaan, moet u een actieve scrobble provider geconfigureerd. Alle verwerkte gebeurtenissen worden na een maand gewist. Als er niet-verwerkte gebeurtenissen zijn, is het waarschijnlijk dat deze stroomopwaarts geen matches kunnen vormen. Neem contact op met uw beheerder om ze te laten corrigeren.", + "description": "Hier vindt u alle scrobble-evenementen die aan uw account zijn gekoppeld. Om evenementen te laten bestaan, moet u een actieve scrobble provider geconfigureerd hebben. Alle verwerkte gebeurtenissen worden na een maand gewist. Als er niet-verwerkte gebeurtenissen zijn, is het waarschijnlijk dat deze online geen matches kunnen vormen. Neem contact op met uw beheerder om ze te laten corrigeren.", "filter-label": "Filter", "created-header": "Gemaakt", "last-modified-header": "Laatst gewijzigd", @@ -111,12 +111,12 @@ "scaling-option-tooltip": "Hoe de afbeelding naar uw scherm te schalen.", "page-splitting-label": "Pagina splitsen", "page-splitting-tooltip": "Een afbeelding over de volledige breedte splitsen (dwz zowel de linker- als de rechterafbeelding worden gecombineerd)", - "reading-mode-label": "", - "layout-mode-label": "", - "layout-mode-tooltip": "", - "background-color-label": "", - "auto-close-menu-label": "", - "show-screen-hints-label": "", + "reading-mode-label": "Leesmodus", + "layout-mode-label": "lay-out modus", + "layout-mode-tooltip": "Render een enkele afbeelding op het scherm of twee naast elkaar geplaatste afbeeldingen", + "background-color-label": "Achtergrondkleur", + "auto-close-menu-label": "Menu automatisch sluiten", + "show-screen-hints-label": "Toon schermhints", "emulate-comic-book-label": "", "swipe-to-paginate-label": "", "book-reader-settings-title": "", @@ -866,455 +866,450 @@ "off": "Uit", "disabled": "Uitgeschakeld", "format-title": "Indeling", - "last-read-title": "", - "length-title": "", - "read-time-title": "", - "less-than-hour": "", - "hour": "", - "hours": "", - "time-left-title": "", - "ongoing": "", - "pages-count": "", - "words-count": "" + "last-read-title": "Laatst gelezen", + "length-title": "Lengte", + "read-time-title": "Leestijd", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "time-left-title": "Resterende tijd", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}" }, "bulk-add-to-collection": { - "title": "", - "promoted": "", - "close": "", - "filter-label": "", - "clear": "", - "no-data": "", - "loading": "", - "collection-label": "", - "create": "" + "title": "Toevoegen aan collectie", + "promoted": "{{common.promoted}}", + "close": "{{common.close}}", + "filter-label": "Filter", + "clear": "{{common.clear}}", + "no-data": "Nog geen collecties aangemaakt", + "loading": "{{common.loading}}", + "collection-label": "Collectie", + "create": "{{common.create}}" }, "entity-title": { - "special": "", - "issue-num": "", - "chapter": "" + "special": "Speciaal", + "issue-num": "Uitgave #", + "chapter": "Hoofdstuk" }, "external-series-card": { - "open-external": "" + "open-external": "Open extern" }, "list-item": { "read": "{{common.read}}" }, "manage-alerts": { - "description-part-1": "", + "description-part-1": "Deze tabel bevat problemen die zijn gevonden tijdens het scannen of lezen van uw media. Deze lijst wordt niet beheerd. U kunt het op elk moment wissen en Library (Force) Scan gebruiken om analyses uit te voeren. Een lijst met enkele veelvoorkomende fouten en hun betekenis is te vinden op de ", "description-part-2": "wiki.", - "filter-label": "", - "clear-alerts": "", - "extension-header": "", - "file-header": "", - "comment-header": "", - "details-header": "" + "filter-label": "Filter", + "clear-alerts": "Meldingen wissen", + "extension-header": "Extensie", + "file-header": "Bestand", + "comment-header": "Opmerking", + "details-header": "Details" }, "manage-email-settings": { - "title": "", - "description": "", - "send-to-warning": "", - "email-url-label": "", - "email-url-tooltip": "", + "title": "E-mailservices (SMTP)", + "description": "Kavita komt standaard met een e-mailservice om taken mogelijk te maken, zoals het uitnodigen van gebruikers, verzoeken om wachtwoord opnieuw in te stellen, enz. E-mails die via onze service worden verzonden, worden onmiddellijk verwijderd. U kunt uw eigen e-mailservice gebruiken door de {{link}} service in te stellen. Stel de URL van de e-mailservice in en gebruik de testknop om te controleren of deze werkt. U kunt deze instellingen op elk moment terugzetten naar de standaardinstellingen. Er is geen manier om e-mails voor authenticatie uit te schakelen, hoewel u niet verplicht bent om een geldig e-mailadres voor gebruikers te gebruiken. Bevestigingslinks worden altijd opgeslagen in logboeken en gepresenteerd in de gebruikersinterface. Registratie-/bevestigings-e-mails worden niet verzonden als u Kavita niet opent via een openbaar bereikbare URL of tenzij de functie Hostnaam is geconfigureerd.", + "send-to-warning": "Als u Verzenden naar apparaat wilt laten werken, moet u uw eigen e-mailservice hosten.", + "email-url-label": "E-mailservice-URL", + "email-url-tooltip": "Gebruik de volledig gekwalificeerde URL van de e-mailservice. Voeg geen schuine streep aan het einde toe.", "reset": "{{common.reset}}", - "test": "", - "host-name-label": "", - "host-name-tooltip": "", - "host-name-validation": "", + "test": "Test", + "host-name-label": "Hostnaam", + "host-name-tooltip": "Domeinnaam (van Reverse Proxy). Indien ingesteld, zal het genereren van e-mail dit altijd gebruiken.", + "host-name-validation": "Hostnaam moet beginnen met http(s) en niet eindigen op /", "reset-to-default": "{{common.reset-to-default}}", "save": "{{common.save}}" }, "manage-library": { - "title": "", - "add-library": "", - "no-data": "", + "title": "Bibliotheken", + "add-library": "Bibliotheek toevoegen", + "no-data": "Geen bibliotheken. Probeer er een te creëren.", "loading": "{{common.loading}}", - "last-scanned-title": "", - "shared-folders-title": "", - "type-title": "", - "scan-library": "", - "delete-library": "", - "delete-library-by-name": "", - "edit-library": "", - "edit-library-by-name": "" + "last-scanned-title": "Laatst gescand:", + "shared-folders-title": "Gedeelde mappen:", + "type-title": "Type:", + "scan-library": "Scan bibliotheek", + "delete-library": "Verwijder bibliotheek", + "delete-library-by-name": "Verwijder {{naam}}", + "edit-library": "Bewerk", + "edit-library-by-name": "Verwijder {{naam}}" }, "manage-media-settings": { - "encode-as-description-part-1": "", - "encode-as-description-part-2": "", - "encode-as-description-part-3": "", - "encode-as-warning": "", - "media-warning": "", - "encode-as-label": "", - "encode-as-tooltip": "", - "bookmark-dir-label": "", - "bookmark-dir-tooltip": "", - "change": "", - "reset-to-default": "", - "reset": "", - "save": "", - "media-issue-title": "", - "scrobble-issue-title": "" + "encode-as-description-part-1": "WebP/AVIF kan de benodigde ruimte voor bestanden drastisch verminderen. WebP/AVIF wordt niet door alle browsers of versies ondersteund. Als u wilt weten of deze instellingen geschikt zijn voor uw installatie, gaat u naar ", + "encode-as-description-part-2": "Kan ik WebP gebruiken?", + "encode-as-description-part-3": "Kan ik AVIF gebruiken?", + "encode-as-warning": "U kunt niet terug converteren naar PNG als u eenmaal naar WebP/AVIF bent gegaan. U moet omslagen in uw bibliotheken vernieuwen om alle omslagen opnieuw te genereren. Bladwijzers en favicons kunnen niet worden geconverteerd.", + "media-warning": "U moet de mediaconversietaak activeren op het tabblad Taken.,", + "encode-as-label": "Media opslaan als", + "encode-as-tooltip": "Alle media die Kavita beheert (omslagen, bladwijzers, favicons) worden als dit type gecodeerd.", + "bookmark-dir-label": "Lijst met bladwijzers", + "bookmark-dir-tooltip": "Locatie waar bladwijzers worden opgeslagen. Bladwijzers zijn bronbestanden en kunnen groot zijn. Kies een locatie met voldoende opslagruimte. Directory wordt beheerd; andere bestanden in de map worden verwijderd. Docker installatie? Koppel dan een extra volume en gebruik deze.", + "change": "Wijziging", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "media-issue-title": "Media-problemen", + "scrobble-issue-title": "Scrobble-problemen" }, "manage-scrobble-errors": { - "description": "", - "filter-label": "", - "clear-errors": "", - "series-header": "", - "created-header": "", - "comment-header": "", - "edit-header": "", - "edit-item-alt": "" + "description": "Deze tabel bevat problemen die zijn gevonden tijdens het scrobbelen. Deze lijst wordt niet beheerd. Je kunt het op elk moment wissen en wachten op de volgende scrobble-upload om te zien. Als er een onbekende serie is, corrigeer je de serienaam of gelokaliseerde serienaam of voeg je een weblink toe voor de providers.", + "filter-label": "Filter", + "clear-errors": "Wis fouten", + "series-header": "Series", + "created-header": "Gemaakt", + "comment-header": "Opmerking", + "edit-header": "Bewerk", + "edit-item-alt": "Bewerk {{seriesName}}" }, "default-date-pipe": { - "never": "" + "never": "Nooit" }, "manage-settings": { - "notice": "", - "restart-required": "", - "base-url-label": "", - "base-url-tooltip": "", - "ip-address-label": "", - "ip-address-tooltip": "", - "port-label": "", - "port-tooltip": "", - "backup-label": "", - "backup-tooltip": "", - "log-label": "", - "log-tooltip": "", - "logging-level-label": "", - "logging-level-tooltip": "", - "cache-size-label": "", - "cache-size-tooltip": "", - "on-deck-last-progress-label": "", - "on-deck-last-progress-tooltip": "", - "on-deck-last-chapter-add-label": "", - "on-deck-last-chapter-add-tooltip": "", - "allow-stats-label": "", - "allow-stats-tooltip-part-1": "", - "allow-stats-tooltip-part-2": "", - "send-data": "", - "opds-label": "", - "opds-tooltip": "", - "enable-opds": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "enable-folder-watching": "", - "reset-to-default": "", - "reset": "", - "save": "", - "cache-size-validation": "", - "field-required": "", - "max-logs-validation": "", - "min-logs-validation": "", - "min-days-validation": "", - "min-cache-validation": "", - "max-backup-validation": "", - "min-backup-validation": "", - "ip-address-validation": "", - "base-url-validation": "" + "notice": "Opmerking:", + "restart-required": "Het wijzigen van de poort, basis-URL, cachegrootte of IP's vereist een handmatige herstart van Kavita om van kracht te worden.", + "base-url-label": "Basis-URL", + "base-url-tooltip": "Gebruik dit als u Kavita op een basis-URL wilt hosten, bijvoorbeeld uwdomein.com/kavita. Niet ondersteund op Docker met niet-rootgebruiker.", + "ip-address-label": "IP-adressen", + "ip-address-tooltip": "Door komma's gescheiden lijst met IP-adressen waarnaar de server luistert. Dit is opgelost als u Docker gebruikt. Vereist opnieuw opstarten om van kracht te worden.", + "port-label": "Poort", + "port-tooltip": "Poort waarop de server luistert. Dit is opgelost als u Docker gebruikt. Vereist opnieuw opstarten om van kracht te worden.", + "backup-label": "Dagen van back-ups", + "backup-tooltip": "Het aantal back-ups dat moet worden onderhouden. Standaard is 30, minimum is 1, maximum is 30.", + "log-label": "Dagen van logboeken", + "log-tooltip": "Het aantal logboeken dat moet worden onderhouden. Standaard is 30, minimum is 1, maximum is 30.", + "logging-level-label": "Logniveau", + "logging-level-tooltip": "Gebruik foutopsporing om problemen te identificeren. Debuggen kan veel schijfruimte in beslag nemen.", + "cache-size-label": "Cache grootte", + "cache-size-tooltip": "De hoeveelheid geheugen die is toegestaan voor het cachen van zware API's. Standaard is 75 MB.", + "on-deck-last-progress-label": "Aan Het Lezen Laatste voortgang (dagen)", + "on-deck-last-progress-tooltip": "Het aantal dagen sinds de laatste voortgang voordat iets van Aan Het Lezen aftrad.", + "on-deck-last-chapter-add-label": "Aan Het Lezen Laatste hoofdstuk toevoegen (dagen)", + "on-deck-last-chapter-add-tooltip": "Het aantal dagen sinds het laatste hoofdstuk is toegevoegd om iets Aan Het Lezen op te nemen.", + "allow-stats-label": "Anonieme gebruiksverzameling toestaan", + "allow-stats-tooltip-part-1": "Stuur anonieme gebruiksgegevens naar de servers van Kavita. Dit omvat informatie over bepaalde gebruikte functies, aantal bestanden, OS-versie, Kavita-installatieversie, CPU en geheugen. We zullen deze informatie gebruiken om prioriteit te geven aan functies, bugfixes en prestatieafstemming. Vereist opnieuw opstarten om van kracht te worden. Zie de ", + "allow-stats-tooltip-part-2": "voor wat er wordt verzameld.", + "send-data": "Verstuur data", + "opds-label": "OPDS", + "opds-tooltip": "Dankzij OPDS-ondersteuning kunnen alle gebruikers OPDS gebruiken om inhoud van de server te lezen en te downloaden.", + "enable-opds": "OPDS inschakelen", + "folder-watching-label": "Mapbewaking", + "folder-watching-tooltip": "Stelt Kavita in staat bibliotheekmappen te bewaken om wijzigingen te detecteren en scannen op die wijzigingen uit te voeren. Hierdoor kan inhoud worden bijgewerkt zonder handmatig scans uit te voeren of te wachten op nachtelijke scans.", + "enable-folder-watching": "Mapbewaking inschakelen", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "Je moet minimaal 50 MB hebben.", + "field-required": "{{validation.field-required}}", + "max-logs-validation": "U kunt niet meer dan {{num}} logboeken hebben", + "min-logs-validation": "U moet minimaal 1 logboek hebben", + "min-days-validation": "Moet minimaal 1 dag zijn", + "min-cache-validation": "Moet 50 MB zijn.", + "max-backup-validation": "U kunt niet meer dan {{num}} back-up hebben", + "min-backup-validation": "U moet minimaal 1 back-up hebben", + "ip-address-validation": "IP-adressen kunnen alleen geldige IPv4- of IPv6-adressen bevatten", + "base-url-validation": "Basis-URL moet beginnen en eindigen met /" }, "manage-system": { - "title": "", - "version-title": "", - "installId-title": "", - "more-info-title": "", - "home-page-title": "", - "wiki-title": "", - "discord-title": "", - "donations-title": "", - "source-title": "", - "feature-request-title": "" + "title": "Over systeem", + "version-title": "Versie", + "installId-title": "Installeer ID", + "more-info-title": "Meer informatie", + "home-page-title": "Startpagina:", + "wiki-title": "Wiki:", + "discord-title": "Discord:", + "donations-title": "Donaties:", + "source-title": "Bron:", + "feature-request-title": "Functieverzoeken" }, "manage-tasks-settings": { - "title": "", - "library-scan-label": "", - "library-scan-tooltip": "", - "library-database-backup-label": "", - "library-database-backup-tooltip": "", - "adhoc-tasks-title": "", - "job-title-header": "", - "description-header": "", - "action-header": "", - "reset-to-default": "", - "reset": "", - "save": "", - "recurring-tasks-title": "", - "last-executed-header": "", - "cron-header": "", - "convert-media-task": "", - "convert-media-task-desc": "", - "convert-media-success": "", - "bust-cache-task": "", - "bust-cache-task-desc": "", - "bust-cache-task-success": "", - "clear-reading-cache-task": "", - "clear-reading-cache-task-desc": "", - "clear-reading-cache-task-success": "", - "clean-up-want-to-read-task": "", - "clean-up-want-to-read-task-desc": "", - "clean-up-want-to-read-task-success": "", - "backup-database-task": "", - "backup-database-task-desc": "", - "backup-database-task-success": "", - "download-logs-task": "", - "download-logs-task-desc": "", - "analyze-files-task": "", - "analyze-files-task-desc": "", - "analyze-files-task-success": "", - "check-for-updates-task": "", - "check-for-updates-task-desc": "" + "title": "Terugkerende taken", + "library-scan-label": "Bibliotheek Scan", + "library-scan-tooltip": "Hoe vaak Kavita metadata van bibliotheekbestanden zal scannen en vernieuwen.", + "library-database-backup-label": "Back-up van bibliotheekdatabase", + "library-database-backup-tooltip": "Hoe vaak Kavita een back-up van de database maakt.", + "adhoc-tasks-title": "Ad-hoctaken", + "job-title-header": "Taak Titel", + "description-header": "Beschrijving", + "action-header": "Actie", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}", + "last-executed-header": "Laatst uitgevoerd", + "cron-header": "Cron", + "convert-media-task": "Converteer media naar doelcodering", + "convert-media-task-desc": "Voert een langlopende taak uit die alle door kavita beheerde media zal converteren naar de doelcodering. Dit is traag (vooral op ARM-apparaten).", + "convert-media-success": "Conversie van media naar doelcodering is in de wachtrij geplaatst", + "bust-cache-task": "Breek Cache", + "bust-cache-task-desc": "Breekt de Kavita+ Cache kapot - mag alleen worden gebruikt bij het debuggen van slechte matches.", + "bust-cache-task-success": "Kavita+ Cache gebroken", + "clear-reading-cache-task": "Leescache wissen", + "clear-reading-cache-task-desc": "Wist bestanden in de cache om te lezen. Handig wanneer u zojuist een bestand hebt bijgewerkt dat u eerder in de afgelopen 24 uur aan het lezen was.", + "clear-reading-cache-task-success": "Cache is gewist", + "clean-up-want-to-read-task": "Opruimen Wil lezen", + "clean-up-want-to-read-task-desc": "Verwijdert alle series die gebruikers volledig hebben gelezen en die binnen Wil lezen vallen en de publicatiestatus Voltooid hebben. Draait elke 24 uur.", + "clean-up-want-to-read-task-success": "Wil lezen is opgeruimd", + "backup-database-task": "Back-up database", + "backup-database-task-desc": "Maakt een back-up van de database, bladwijzers, thema's, handmatig geüploade omslagen en configuratiebestanden.", + "backup-database-task-success": "Een taak voor het maken van een back-up van de database is in de wachtrij geplaatst", + "download-logs-task": "Logboeken downloaden", + "download-logs-task-desc": "Compileert alle logbestanden in een zip en downloadt deze.", + "analyze-files-task": "Analyseer bestanden", + "analyze-files-task-desc": "Voert een langlopende taak uit die bestanden analyseert om extensie en grootte te genereren. Dit mag slechts één keer worden uitgevoerd voor de v0.7-release. Niet nodig als je post v0.7 hebt geïnstalleerd.", + "analyze-files-task-success": "Bestandsanalyse is in de wachtrij geplaatst", + "check-for-updates-task": "Controleer op updates", + "check-for-updates-task-desc": "Kijk of er stabiele releases zijn vóór uw versie." }, "manage-users": { - "title": "", - "invite": "", - "you-alt": "", - "pending-title": "", - "delete-user-tooltip": "", - "delete-user-alt": "", - "edit-user-tooltip": "", - "edit-user-alt": "", - "resend-invite-tooltip": "", - "resend-invite-alt": "", - "setup-user-tooltip": "", - "setup-user-alt": "", - "change-password-tooltip": "", - "change-password-alt": "", - "resend": "", - "setup": "", - "last-active-title": "", - "roles-title": "", - "none": "", - "never": "", - "online-now-tooltip": "", - "sharing-title": "", - "no-data": "", - "loading": "" + "title": "Actieve gebruikers", + "invite": "Uitnodigen", + "you-alt": "(Jij)", + "pending-title": "In behandeling", + "delete-user-tooltip": "Verwijder gebruiker", + "delete-user-alt": "Gebruiker {{user}} verwijderen", + "edit-user-tooltip": "Bewerken", + "edit-user-alt": "Bewerk gebruiker {{user}}", + "resend-invite-tooltip": "Uitnodiging opnieuw verzenden", + "resend-invite-alt": "Uitnodiging opnieuw verzenden {{user}}", + "setup-user-tooltip": "Instellen Gebruiker", + "setup-user-alt": "Instellen Gebruiker {{user}}", + "change-password-tooltip": "Verander wachtwoord", + "change-password-alt": "Verander wachtwoord {{user}}", + "resend": "Opnieuw versturen", + "setup": "Instelling", + "last-active-title": "Laatst actief:", + "roles-title": "Rollen:", + "none": "Geen", + "never": "Nooit", + "online-now-tooltip": "Nu online", + "sharing-title": "Delen:", + "no-data": "Er zijn geen andere gebruikers.", + "loading": "{{common.loading}}" }, "edit-collection-tags": { - "title": "", - "required-field": "", - "save": "", - "close": "", - "cancel": "", - "general-tab": "", - "cover-image-tab": "", - "series-tab": "", - "name-label": "", - "name-validation": "", - "promote-label": "", - "promote-tooltip": "", - "summary-label": "", - "series-title": "", - "deselect-all": "", - "select-all": "" + "title": "{{collectionName}} collectie bewerken", + "required-field": "{{validation.required-field}}", + "save": "{{common.save}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "general-tab": "Algemeen", + "cover-image-tab": "Omslagafbeelding", + "series-tab": "Serie", + "name-label": "Naam", + "name-validation": "Naam moet uniek zijn", + "promote-label": "Promoot", + "promote-tooltip": "Promotie betekent dat de tag serverbreed zichtbaar is, niet alleen voor beheerders. Alle series die deze tag hebben, hebben nog steeds beperkingen voor gebruikerstoegang.", + "summary-label": "Samenvatting", + "series-title": "Geldt voor serie", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}" }, "library-detail": { - "library-tab": "", - "recommended-tab": "" + "library-tab": "Bibliotheek", + "recommended-tab": "Aanbevolen" }, "library-recommended": { - "no-data": "", - "more-in-genre": "", - "rediscover": "", - "highly-rated": "", - "quick-catchups": "", - "quick-reads": "", - "on-deck": "" + "no-data": "Hier is niets te zien. Voeg wat metadata toe aan je bibliotheek, lees iets of beoordeel iets. Voor deze bibliotheek zijn aanbevelingen mogelijk ook uitgeschakeld.", + "more-in-genre": "Meer in {{genre}}", + "rediscover": "Herontdekken", + "highly-rated": "Hoog beoordeeld", + "quick-catchups": "Snelle inhaalacties", + "quick-reads": "Snel gelezen", + "on-deck": "{{dashboard.on-deck-title}}" }, "admin-dashboard": { - "title": "", - "general-tab": "", - "users-tab": "", - "libraries-tab": "", - "media-tab": "", - "logs-tab": "", - "email-tab": "", - "tasks-tab": "", + "title": "Beheerdashboard", + "general-tab": "Algemeen", + "users-tab": "Gebruikers", + "libraries-tab": "Bibliotheken", + "media-tab": "Media", + "logs-tab": "Logboeken", + "email-tab": "Email", + "tasks-tab": "Taken", "statistics-tab": "Statistieken", - "system-tab": "", - "kavita+-tab": "", - "kavita+-desc-part-1": "", - "kavita+-desc-part-2": "", - "kavita+-desc-part-3": "" + "system-tab": "Systeem", + "kavita+-tab": "Kavita+", + "kavita+-desc-part-1": "Kavita+ is een premium abonnementsservice die functies ontgrendelt voor alle gebruikers van deze Kavita-instantie. Koop een abonnement om te ontgrendelen ", + "kavita+-desc-part-2": "premium voordelen", + "kavita+-desc-part-3": "Vandaag!" }, "collection-detail": { - "no-data": "", - "no-data-filtered": "", - "title-alt": "" + "no-data": "Hier is nog niks. Probeer nieuwe series toe te voegen.", + "no-data-filtered": "Geen resultaten met huidige filter.", + "title-alt": "Kavita - {{collectionName}} Collectie" }, "all-collections": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "Collecties", + "item-count": "{{common.item-count}}", + "no-data": "Er zijn geen collecties.", + "create-one-part-1": "Probeer te creëren", + "create-one-part-2": "één" }, "carousel-reel": { - "prev-items": "", - "next-items": "" + "prev-items": "Vorige items", + "next-items": "Volgende items" }, "draggable-ordered-list": { - "instructions-alt": "", - "reorder-label": "", - "remove-item-alt": "" + "instructions-alt": "Wanneer u een nummer invoert in de invoer voor opnieuw bestellen, wordt het artikel op die locatie ingevoegd en wordt de volgorde van alle andere artikelen bijgewerkt.", + "reorder-label": "Herordenen", + "remove-item-alt": "Verwijder item" }, "reading-lists": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "Lees lijsten", + "item-count": "{{common.item-count}}", + "no-data": "Er zijn geen leeslijsten.", + "create-one-part-1": "Probeer te creëren", + "create-one-part-2": "één" }, "reading-list-item": { - "remove": "", - "read": "" + "remove": "{{common.remove}}", + "read": "{{common.read}}" }, "reading-list-detail": { - "item-count": "", - "page-settings-title": "", - "remove-read": "", - "order-numbers-label": "", - "continue": "", - "read": "", - "read-options-alt": "", - "incognito-alt": "", - "no-data": "" + "item-count": "{{common.item-count}}", + "page-settings-title": "Pagina Instellingen", + "remove-read": "Verwijder Lezen", + "order-numbers-label": "Volgorde nummers", + "continue": "Doorgaan", + "read": "{{common.read}}", + "read-options-alt": "Lees opties", + "incognito-alt": "(Incognito)", + "no-data": "Niets toegevoegd", + "characters-title": "{{series-metadata-detail.characters-title}}" }, "events-widget": { - "title-alt": "", - "dismiss-all": "", - "update-available": "", - "downloading-item": "", - "more-info": "", - "close": "", - "users-online-count": "", - "active-events-title": "", - "no-data": "" + "title-alt": "Activiteit", + "dismiss-all": "Alles afkeuren", + "update-available": "Update beschikbaar", + "downloading-item": "{{item}} downloaden", + "more-info": "Klik voor meer informatie", + "close": "{{common.close}}", + "users-online-count": "{{num}} Gebruikers online", + "active-events-title": "Actieve gebeurtenissen:", + "no-data": "Er gebeurt hier niet veel" }, "shortcuts-modal": { - "title": "", - "close": "", - "prev-page": "", - "next-page": "", - "go-to": "", - "bookmark": "", - "double-click": "", - "close-reader": "", - "toggle-menu": "" + "title": "Sneltoetsen", + "close": "{{common.close}}", + "prev-page": "Ga naar de vorige pagina", + "next-page": "Ga naar de volgende pagina", + "go-to": "Open het dialoogvenster Ga naar pagina", + "bookmark": "Bookmark de huidige pagina", + "double-click": "Dubbelklik", + "close-reader": "Sluit lezer", + "toggle-menu": "Schakelmenu" }, "grouped-typeahead": { - "files": "", - "chapters": "", - "people": "", - "tags": "", - "genres": "", - "libraries": "", - "reading-lists": "", - "collections": "", - "close": "", - "loading": "" + "files": "Bestanden", + "chapters": "Hoofdstukken", + "people": "Mensen", + "tags": "Labels", + "genres": "Genres", + "libraries": "Bibliotheken", + "reading-lists": "Leeslijsten", + "collections": "Collecties", + "close": "{{common.close}}", + "loading": "{{common.loading}}" }, "nav-header": { - "skip-alt": "", - "search-series-alt": "", - "search-alt": "", - "promoted": "", - "no-data": "", - "scroll-to-top-alt": "", - "server-settings": "", - "settings": "", - "help": "", - "announcements": "", - "logout": "" + "skip-alt": "Ga verder naar de hoofdinhoud", + "search-series-alt": "Zoek serie", + "search-alt": "Zoek…", + "promoted": "(gepromoot)", + "no-data": "Geen resultaten gevonden", + "scroll-to-top-alt": "Scroll naar boven", + "server-settings": "Serverinstellingen", + "settings": "Instellingen", + "help": "Hulp", + "announcements": "Aankondigingen", + "logout": "Uitloggen" }, "add-to-list-modal": { - "title": "", - "close": "", - "filter-label": "", - "promoted-alt": "", - "no-data": "", - "loading": "", - "reading-list-label": "", - "create": "" + "title": "Toevoegen aan leeslijst", + "close": "{{common.close}}", + "filter-label": "Filter", + "promoted-alt": "gepromoot", + "no-data": "Nog geen lijsten gemaakt", + "loading": "{{common.loading}}", + "reading-list-label": "Lees lijst", + "create": "{{common.create}}" }, "edit-reading-list-modal": { - "title": "", - "general-tab": "", - "cover-image-tab": "", - "close": "", - "save": "", - "year-validation": "", - "month-validation": "", - "name-unique-validation": "", - "required-field": "", - "summary-label": "", - "year-label": "", - "month-label": "", - "ending-title": "", - "starting-title": "", - "promote-label": "", - "promote-tooltip": "" + "title": "Leeslijst bewerken: {{name}}", + "general-tab": "Algemeen", + "cover-image-tab": "Omslagfoto", + "close": "{{common.close}}", + "save": "{common.save}}", + "year-validation": "Moet groter zijn dan 1000, 0 of blanco", + "month-validation": "Moet tussen 1 en 12 liggen of blanco zijn", + "name-unique-validation": "Naam moet uniek zijn", + "required-field": "{{validation.required-field}}", + "summary-label": "Samenvatting", + "year-label": "Jaar", + "month-label": "Maand", + "ending-title": "Eindigt", + "starting-title": "Beginnend", + "promote-label": "Promoot", + "promote-tooltip": "Promotie betekent dat de tag serverbreed zichtbaar is, en niet alleen voor beheerders. Voor alle series met deze tag gelden nog steeds gebruikerstoegangsbeperkingen." }, "import-cbl-modal": { - "close": "", - "title": "", - "import-description": "", - "validate-description": "", - "validate-warning": "", - "validate-no-issue": "", - "validate-no-issue-description": "", - "dry-run-description": "", - "prev": "", - "import": "", - "restart": "", - "next": "", - "import-step": "", - "validate-cbl-step": "", - "dry-run-step": "", - "final-import-step": "" + "close": "{{common.close}}", + "title": "CBL-import", + "import-description": "Importeer een .cbl-bestand om aan de slag te gaan. Kavita voert meerdere controles uit voordat het importeert. Sommige stappen blokkeren de voortgang vanwege problemen met het bestand.", + "validate-description": "Alle bestanden zijn gevalideerd om te zien of er bewerkingen in de lijst moeten worden uitgevoerd. Lijsten die zijn mislukt, gaan niet naar de volgende stap. Herstel de CBL-bestanden en probeer het opnieuw.", + "validate-warning": "Er zijn problemen met het CBL die een import verhinderen. Corrigeer deze problemen en probeer het opnieuw.", + "validate-no-issue": "Ziet er goed uit", + "validate-no-issue-description": "Geen problemen gevonden met CBL, druk op Volgende.", + "dry-run-description": "Dit is een testrun en laat zien wat er gebeurt als u op Volgende drukt en de import uitvoert. Niet alle fouten worden geïmporteerd.", + "prev": "Vorige", + "import": "importeer", + "restart": "Herstart", + "next": "Volgende", + "import-step": "Importeer CBL's", + "validate-cbl-step": "Valideer CBL", + "dry-run-step": "Test Run", + "final-import-step": "Laatste stap" }, "pdf-reader": { - "loading-message": "", - "incognito-mode": "", - "light-theme-alt": "", - "dark-theme-alt": "", - "close-reader-alt": "" - }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" + "loading-message": "Het laden van……PDF's kunnen langer duren dan verwacht", + "incognito-mode": "Incognito modus", + "light-theme-alt": "Licht thema", + "dark-theme-alt": "Donker thema", + "close-reader-alt": "Sluit Lezer" }, "manga-reader": { - "back": "", - "save-globally": "", - "incognito-alt": "", - "incognito-title": "", - "shortcuts-menu-alt": "", - "prev-page-tooltip": "", - "next-page-tooltip": "", - "prev-chapter-tooltip": "", - "next-chapter-tooltip": "", - "first-page-tooltip": "", - "last-page-tooltip": "", - "left-to-right-alt": "", - "right-to-left-alt": "", - "reading-direction-tooltip": "", - "reading-mode-tooltip": "", - "collapse": "", - "fullscreen": "", - "settings-tooltip": "", - "image-splitting-label": "", - "image-scaling-label": "", - "height": "", - "width": "", - "original": "", - "auto-close-menu-label": "", - "swipe-enabled-label": "", - "enable-comic-book-label": "", - "brightness-label": "", - "first-time-reading-manga": "", - "layout-mode-switched": "", + "back": "Terug", + "save-globally": "Globaal opslaan", + "incognito-alt": "De incognitomodus staat aan. Schakel om uit te schakelen.", + "incognito-title": "Incognito modus:", + "shortcuts-menu-alt": "Sneltoetsen Modaal", + "prev-page-tooltip": "Vorige bladzijde", + "next-page-tooltip": "Volgende bladzijde", + "prev-chapter-tooltip": "Vorig hoofdstuk/volume", + "next-chapter-tooltip": "Volgend hoofdstuk/volume", + "first-page-tooltip": "Eerste pagina", + "last-page-tooltip": "Laatste pagina", + "left-to-right-alt": "Links naar rechts", + "right-to-left-alt": "Rechts naar links", + "reading-direction-tooltip": "Leesrichting: ", + "reading-mode-tooltip": "Leesmodus", + "collapse": "Samenvouwen", + "fullscreen": "Volledig scherm", + "settings-tooltip": "Instellingen", + "image-splitting-label": "Beeldsplitsing", + "image-scaling-label": "Afbeelding schalen", + "height": "Hoogte", + "width": "Breedte", + "original": "Origineel", + "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "swipe-enabled-label": "Swipen ingeschakeld", + "enable-comic-book-label": "Emuleer stripboek", + "brightness-label": "Helderheid", + "first-time-reading-manga": "Tik op elk gewenst moment op de afbeelding om het menu te openen. U kunt verschillende instellingen configureren of naar de pagina gaan door op de voortgangsbalk te klikken. Tik op de zijkanten van de afbeelding om naar de volgende/vorige pagina te gaan.", + "layout-mode-switched": "De lay-outmodus is overgeschakeld naar Enkel omdat er onvoldoende ruimte is om een dubbele lay-out weer te geven", "no-next-chapter": "", "no-prev-chapter": "", "user-preferences-updated": "" @@ -1322,7 +1317,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", @@ -1636,5 +1630,11 @@ "theme-black": "Zwart", "theme-paper": "Papier", "theme-white": "Wit" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "Scroll omhoog om naar het vorige hoofdstuk te gaan", + "continuous-reading-prev-chapter": "Vorig hoofdstuk", + "continuous-reading-next-chapter-alt": "Scroll omhoog om naar het volgende hoofdstuk te gaan", + "continuous-reading-next-chapter": "Volgende hoofdstuk" } } diff --git a/UI/Web/src/assets/langs/pl.json b/UI/Web/src/assets/langs/pl.json new file mode 100644 index 0000000000..d61c905014 --- /dev/null +++ b/UI/Web/src/assets/langs/pl.json @@ -0,0 +1,1684 @@ +{ + "login": { + "title": "Login", + "username": "{{common.username}}", + "password": "{{common.password}}", + "password-validation": "{{validation.password-validation}}", + "forgot-password": "Zapomniałeś hasła?", + "submit": "{{common.submit}}" + }, + "dashboard": { + "no-libraries": "W tej chwili nie ma zdefiniowanych bibliotek. Zmień ustawienia w", + "server-settings-link": "Ustawienia serwera", + "not-granted": "", + "on-deck-title": "", + "recently-updated-title": "Ostatnio Zaktualizowane Serie", + "recently-added-title": "Nowo Dodane Serie" + }, + "edit-user": { + "edit": "{{common.edit}}", + "close": "{{common.close}}", + "username": "{{common.username}}", + "required": "{{validation.required-field}}", + "email": "{{common.email}}", + "not-valid-email": "{{validation.valid-email}}", + "cancel": "{{common.cancel}}", + "saving": "Zapisuję…", + "update": "Aktualizacja" + }, + "user-scrobble-history": { + "title": "", + "description": "", + "filter-label": "Filtr", + "created-header": "Utworzono", + "last-modified-header": "", + "type-header": "Rodzaj", + "series-header": "Serie", + "data-header": "Data", + "is-processed-header": "", + "no-data": "", + "volume-and-chapter-num": "", + "rating": "", + "not-applicable": "", + "processed": "", + "not-processed": "" + }, + "scrobble-event-type-pipe": { + "chapter-read": "", + "score-updated": "", + "want-to-read-add": "", + "want-to-read-remove": "", + "review": "" + }, + "spoiler": { + "click-to-show": "" + }, + "review-series-modal": { + "title": "", + "tagline-label": "", + "review-label": "", + "close": "{{common.close}}", + "save": "{{common.save}}" + }, + "review-card-modal": { + "close": "{{common.close}}", + "user-review": "", + "external-mod": "", + "go-to-review": "" + }, + "review-card": { + "your-review": "", + "external-review": "", + "local-review": "", + "rating-percentage": "" + }, + "want-to-read": { + "title": "", + "series-count": "{{common.series-count}}", + "no-items": "", + "no-items-filtered": "" + }, + "user-preferences": { + "title": "", + "pref-description": "", + "account-tab": "", + "preferences-tab": "", + "3rd-party-clients-tab": "", + "theme-tab": "", + "devices-tab": "", + "stats-tab": "", + "scrobbling-tab": "", + "success-toast": "", + "global-settings-title": "", + "page-layout-mode-label": "", + "page-layout-mode-tooltip": "", + "locale-label": "", + "locale-tooltip": "", + "blur-unread-summaries-label": "", + "blur-unread-summaries-tooltip": "", + "prompt-on-download-label": "", + "prompt-on-download-tooltip": "", + "disable-animations-label": "", + "disable-animations-tooltip": "", + "collapse-series-relationships-label": "", + "collapse-series-relationships-tooltip": "", + "share-series-reviews-label": "", + "share-series-reviews-tooltip": "", + "image-reader-settings-title": "", + "reading-direction-label": "", + "reading-direction-tooltip": "", + "scaling-option-label": "", + "scaling-option-tooltip": "", + "page-splitting-label": "", + "page-splitting-tooltip": "", + "reading-mode-label": "", + "layout-mode-label": "", + "layout-mode-tooltip": "", + "background-color-label": "", + "auto-close-menu-label": "", + "show-screen-hints-label": "", + "emulate-comic-book-label": "", + "swipe-to-paginate-label": "", + "book-reader-settings-title": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "reading-direction-book-label": "", + "reading-direction-book-tooltip": "", + "font-family-label": "", + "font-family-tooltip": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "layout-mode-book-label": "", + "layout-mode-book-tooltip": "", + "color-theme-book-label": "", + "color-theme-book-tooltip": "", + "font-size-book-label": "", + "line-height-book-label": "", + "line-height-book-tooltip": "", + "margin-book-label": "", + "margin-book-tooltip": "", + "clients-opds-alert": "", + "clients-opds-description": "", + "clients-api-key-tooltip": "", + "clients-opds-url-tooltip": "", + "reset": "{{common.reset}}", + "save": "{{common.save}}" + }, + "user-holds": { + "title": "", + "description": "" + }, + "theme-manager": { + "title": "", + "looking-for-theme": "", + "looking-for-theme-continued": "", + "scan": "", + "site-themes": "", + "set-default": "", + "apply": "{{common.apply}}", + "applied": "", + "updated-toastr": "", + "scan-queued": "" + }, + "theme": { + "theme-dark": "", + "theme-black": "", + "theme-paper": "", + "theme-white": "" + }, + "restriction-selector": { + "title": "", + "description": "", + "not-applicable-for-admins": "", + "age-rating-label": "", + "no-restriction": "", + "include-unknowns-label": "", + "include-unknowns-tooltip": "" + }, + "site-theme-provider-pipe": { + "system": "", + "user": "" + }, + "manage-devices": { + "title": "", + "description": "", + "devices-title": "", + "no-devices": "", + "platform-label": "", + "email-label": "", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" + }, + "edit-device": { + "device-name-label": "", + "email-label": "{{common.email}}", + "email-tooltip": "", + "device-platform-label": "", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" + }, + "change-password": { + "password-label": "{{common.password}}", + "current-password-label": "", + "new-password-label": "", + "confirm-password-label": "", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "", + "permission-error": "" + }, + "change-email": { + "email-label": "{{common.email}}", + "current-password-label": "", + "email-not-confirmed": "", + "email-updated-title": "", + "email-updated-description": "", + "setup-user-account": "", + "invite-url-label": "", + "invite-url-tooltip": "", + "permission-error": "", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "change-age-restriction": { + "age-restriction-label": "", + "unknowns": "", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "api-key": { + "copy": "", + "regen-warning": "", + "no-key": "", + "confirm-reset": "", + "key-reset": "" + }, + "scrobbling-providers": { + "title": "", + "requires": "", + "token-expired": "", + "no-token-set": "", + "token-set": "", + "generate": "", + "instructions": "", + "token-input-label": "", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "typeahead": { + "locked-field": "", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "add-item": "", + "no-data": "", + "add-custom-item": "" + }, + "generic-list-modal": { + "close": "{{common.close}}", + "clear": "", + "filter": "", + "open-filtered-search": "" + }, + "user-stats-info-cards": { + "total-pages-read-label": "", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", + "total-words-read-label": "", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "avg-reading-per-week-label": "", + "last-active-label": "", + "chapters": "" + }, + "user-stats": { + "library-read-progress-title": "", + "read-percentage": "" + }, + "top-readers": { + "title": "", + "time-selection-label": "", + "comics-label": "", + "manga-label": "", + "books-label": "", + "this-week": "", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" + }, + "role-selector": { + "title": "" + }, + "directory-picker": { + "title": "", + "close": "{{common.close}}", + "path-label": "", + "path-placeholder": "", + "instructions": "", + "type-header": "", + "name-header": "", + "cancel": "{{common.cancel}}", + "share": "", + "help": "{{common.help}}" + }, + "library-access-modal": { + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "", + "close": "{{common.close}}", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "no-data": "" + }, + "time-periods": { + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "device-platform-pipe": { + "custom": "" + }, + "day-of-week-pipe": { + "monday": "", + "tuesday": "", + "wednesday": "", + "thursday": "", + "friday": "", + "saturday": "", + "sunday": "" + }, + "cbl-import-result-pipe": { + "success": "", + "partial": "", + "failure": "" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "", + "chapter-missing": "", + "empty-file": "", + "name-conflict": "", + "series-collision": "", + "series-missing": "", + "volume-missing": "", + "all-chapter-missing": "", + "invalid-file": "", + "success": "" + }, + "time-duration-pipe": { + "hours": "", + "minutes": "", + "days": "", + "months": "", + "years": "" + }, + "time-ago-pipe": { + "never": "", + "just-now": "", + "min-ago": "", + "mins-ago": "", + "hour-ago": "", + "hours-ago": "", + "day-ago": "", + "days-ago": "", + "month-ago": "", + "months-ago": "", + "year-ago": "", + "years-ago": "" + }, + "relationship-pipe": { + "adaptation": "", + "alternative-setting": "", + "alternative-version": "", + "character": "", + "contains": "", + "doujinshi": "", + "other": "", + "prequel": "", + "sequel": "", + "side-story": "", + "spin-off": "", + "parent": "", + "edition": "" + }, + "publication-status-pipe": { + "ongoing": "", + "hiatus": "", + "completed": "", + "cancelled": "", + "ended": "" + }, + "person-role-pipe": { + "artist": "", + "character": "", + "colorist": "", + "cover-artist": "", + "editor": "", + "inker": "", + "letterer": "", + "penciller": "", + "publisher": "", + "writer": "", + "other": "" + }, + "manga-format-pipe": { + "epub": "", + "archive": "", + "image": "", + "pdf": "", + "unknown": "" + }, + "library-type-pipe": { + "book": "", + "comic": "", + "manga": "" + }, + "age-rating-pipe": { + "unknown": "", + "early-childhood": "", + "adults-only": "", + "everyone": "", + "everyone-10-plus": "", + "g": "", + "kids-to-adults": "", + "mature": "", + "ma15-plus": "", + "mature-17-plus": "", + "rating-pending": "", + "teen": "", + "x18-plus": "", + "not-applicable": "", + "pg": "", + "r18-plus": "" + }, + "reset-password": { + "title": "", + "description": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "submit": "" + }, + "reset-password-modal": { + "title": "", + "new-password-label": "", + "error-label": "", + "close": "", + "cancel": "", + "save": "" + }, + "all-series": { + "series-count": "" + }, + "announcements": { + "title": "" + }, + "changelog": { + "installed": "", + "download": "", + "published-label": "", + "available": "", + "description": "", + "description-continued": "" + }, + "invite-user": { + "title": "", + "close": "", + "description": "", + "email": "", + "required-field": "", + "setup-user-title": "", + "setup-user-description": "", + "setup-user-account": "", + "setup-user-account-tooltip": "", + "invite-url-label": "", + "invite": "", + "inviting": "", + "cancel": "" + }, + "library-selector": { + "title": "", + "select-all": "", + "deselect-all": "", + "no-data": "" + }, + "license": { + "title": "", + "manage": "", + "invalid-license-tooltip": "", + "check": "", + "cancel": "", + "edit": "", + "buy": "", + "activate": "", + "renew": "", + "no-license-key": "", + "license-valid": "", + "license-not-valid": "", + "loading": "", + "activate-description": "", + "activate-license-label": "", + "activate-email-label": "", + "activate-delete": "", + "activate-save": "" + }, + "book-line-overlay": { + "copy": "", + "bookmark": "", + "close": "", + "required-field": "", + "bookmark-label": "", + "save": "" + }, + "book-reader": { + "title": "", + "page-label": "", + "pagination-header": "", + "go-to-page": "", + "go-to-last-page": "", + "prev-page": "", + "next-page": "", + "prev-chapter": "", + "next-chapter": "", + "skip-header": "", + "virtual-pages": "", + "settings-header": "", + "table-of-contents-header": "", + "bookmarks-header": "", + "toc-header": "", + "loading-book": "", + "go-back": "", + "incognito-mode-alt": "", + "incognito-mode-label": "", + "next": "", + "previous": "", + "go-to-page-prompt": "" + }, + "personal-table-of-contents": { + "no-data": "", + "page": "", + "delete": "" + }, + "confirm-email": { + "title": "", + "description": "", + "error-label": "", + "username-label": "", + "password-label": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "confirm-email-change": { + "title": "", + "non-confirm-description": "", + "confirm-description": "", + "success": "" + }, + "confirm-reset-password": { + "title": "", + "description": "", + "password-label": "", + "required-field": "", + "submit": "", + "password-validation": "" + }, + "register": { + "title": "", + "description": "", + "username-label": "", + "email-label": "", + "email-tooltip": "", + "password-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "series-detail": { + "page-settings-title": "", + "close": "", + "layout-mode-label": "", + "layout-mode-option-card": "", + "layout-mode-option-list": "", + "continue-from": "", + "read": "", + "continue": "", + "read-options-alt": "", + "incognito": "", + "remove-from-want-to-read": "", + "add-to-want-to-read": "", + "edit-series-alt": "", + "download-series--tooltip": "", + "downloading-status": "", + "user-reviews-alt": "", + "storyline-tab": "", + "books-tab": "", + "volumes-tab": "", + "specials-tab": "", + "related-tab": "", + "recommendations-tab": "", + "send-to": "", + "no-pages": "", + "no-chapters": "", + "cover-change": "" + }, + "series-metadata-detail": { + "links-title": "", + "genres-title": "", + "tags-title": "", + "collections-title": "", + "reading-lists-title": "", + "writers-title": "", + "cover-artists-title": "", + "characters-title": "", + "colorists-title": "", + "editors-title": "", + "inkers-title": "", + "letterers-title": "", + "translators-title": "", + "pencillers-title": "", + "publishers-title": "", + "promoted": "", + "see-more": "", + "see-less": "" + }, + "badge-expander": { + "more-items": "" + }, + "read-more": { + "read-more": "", + "read-less": "" + }, + "update-notification-modal": { + "title": "", + "close": "", + "help": "", + "download": "" + }, + "side-nav-companion-bar": { + "page-settings-title": "", + "open-filter-and-sort": "", + "close-filter-and-sort": "", + "filter-and-sort-alt": "" + }, + "side-nav": { + "home": "", + "want-to-read": "", + "collections": "", + "reading-lists": "", + "bookmarks": "", + "filter-label": "", + "all-series": "", + "clear": "", + "donate": "" + }, + "library-settings-modal": { + "close": "", + "edit-title": "", + "add-title": "", + "general-tab": "", + "folder-tab": "", + "cover-tab": "", + "advanced-tab": "", + "name-label": "", + "library-name-unique": "", + "last-scanned-label": "", + "type-label": "", + "type-tooltip": "", + "folder-description": "", + "browse": "", + "help-us-part-1": "", + "help-us-part-2": "", + "help-us-part-3": "", + "naming-conventions-part-1": "", + "naming-conventions-part-2": "", + "naming-conventions-part-3": "", + "cover-description": "", + "cover-description-extra": "", + "manage-collection-label": "", + "manage-collection-tooltip": "", + "manage-reading-list-label": "", + "manage-reading-list-tooltip": "", + "allow-scrobbling-label": "", + "allow-scrobbling-tooltip": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "include-in-dashboard-label": "", + "include-in-dashboard-tooltip": "", + "include-in-recommendation-label": "", + "include-in-recommendation-tooltip": "", + "include-in-search-label": "", + "include-in-search-tooltip": "", + "force-scan": "", + "force-scan-tooltip": "", + "reset": "", + "cancel": "", + "next": "", + "save": "", + "required-field": "" + }, + "reader-settings": { + "general-settings-title": "", + "font-family-label": "", + "font-size-label": "", + "line-spacing-label": "", + "margin-label": "", + "reset-to-defaults": "", + "reader-settings-title": "", + "reading-direction-label": "", + "right-to-left": "", + "left-to-right": "", + "horizontal": "", + "vertical": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "on": "", + "off": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "fullscreen-label": "", + "fullscreen-tooltip": "", + "exit": "", + "enter": "", + "layout-mode-label": "", + "layout-mode-tooltip": "", + "layout-mode-option-scroll": "", + "layout-mode-option-1col": "", + "layout-mode-option-2col": "", + "color-theme-title": "", + "theme-dark": "", + "theme-black": "", + "theme-white": "", + "theme-paper": "" + }, + "table-of-contents": { + "no-data": "" + }, + "bookmarks": { + "title": "", + "series-count": "", + "no-data": "", + "no-data-2": "", + "confirm-delete": "", + "confirm-single-delete": "", + "delete-success": "", + "delete-single-success": "" + }, + "bulk-operations": { + "title": "", + "items-selected": "", + "mark-as-unread": "", + "mark-as-read": "", + "deselect-all": "" + }, + "card-detail-drawer": { + "general-tab": "", + "metadata-tab": "", + "cover-tab": "", + "info-tab": "", + "no-summary": "", + "writers-title": "", + "genres-title": "", + "publishers-title": "", + "tags-title": "", + "not-defined": "", + "read": "", + "unread": "", + "files": "", + "pages": "", + "added": "", + "size": "" + }, + "card-detail-layout": { + "total-items": "" + }, + "card-item": { + "cannot-read": "" + }, + "chapter-metadata-detail": { + "no-data": "", + "writers-title": "", + "publishers-title": "", + "characters-title": "", + "translators-title": "", + "letterers-title": "", + "colorists-title": "", + "inkers-title": "", + "pencillers-title": "", + "cover-artists-title": "", + "editors-title": "" + }, + "cover-image-chooser": { + "drag-n-drop": "", + "upload": "", + "upload-continued": "", + "url-label": "", + "load": "", + "back": "", + "reset-cover-tooltip": "", + "reset": "", + "image-num": "", + "apply": "", + "applied": "" + }, + "download-indicator": { + "progress": "" + }, + "edit-series-relation": { + "description-part-1": "", + "description-part-2": "", + "target-series": "", + "relationship": "", + "remove": "", + "add-relationship": "", + "parent": "" + }, + "entity-info-cards": { + "tags-title": "", + "characters-title": "", + "release-date-title": "", + "release-date-tooltip": "", + "age-rating-title": "", + "length-title": "", + "pages-count": "", + "words-count": "", + "reading-time-title": "", + "date-added-title": "", + "size-title": "", + "id-title": "", + "links-title": "", + "isbn-title": "", + "last-read-title": "", + "less-than-hour": "", + "range-hours": "", + "hour": "", + "hours": "", + "read-time-title": "" + }, + "series-info-cards": { + "release-date-title": "", + "release-year-tooltip": "", + "age-rating-title": "", + "language-title": "", + "publication-status-title": "", + "publication-status-tooltip": "", + "scrobbling-title": "", + "scrobbling-tooltip": "", + "on": "", + "off": "", + "disabled": "", + "format-title": "", + "last-read-title": "", + "length-title": "", + "read-time-title": "", + "less-than-hour": "", + "hour": "", + "hours": "", + "time-left-title": "", + "ongoing": "", + "pages-count": "", + "words-count": "" + }, + "bulk-add-to-collection": { + "title": "", + "promoted": "", + "close": "", + "filter-label": "", + "clear": "", + "no-data": "", + "loading": "", + "collection-label": "", + "create": "" + }, + "entity-title": { + "special": "", + "issue-num": "", + "chapter": "" + }, + "external-series-card": { + "open-external": "" + }, + "list-item": { + "read": "" + }, + "manage-alerts": { + "description-part-1": "", + "description-part-2": "", + "filter-label": "", + "clear-alerts": "", + "extension-header": "", + "file-header": "", + "comment-header": "", + "details-header": "" + }, + "manage-email-settings": { + "title": "", + "description": "", + "send-to-warning": "", + "email-url-label": "", + "email-url-tooltip": "", + "reset": "", + "test": "", + "host-name-label": "", + "host-name-tooltip": "", + "host-name-validation": "", + "reset-to-default": "", + "save": "" + }, + "manage-library": { + "title": "", + "add-library": "", + "no-data": "", + "loading": "", + "last-scanned-title": "", + "shared-folders-title": "", + "type-title": "", + "scan-library": "", + "delete-library": "", + "delete-library-by-name": "", + "edit-library": "", + "edit-library-by-name": "" + }, + "manage-media-settings": { + "encode-as-description-part-1": "", + "encode-as-description-part-2": "", + "encode-as-description-part-3": "", + "encode-as-warning": "", + "media-warning": "", + "encode-as-label": "", + "encode-as-tooltip": "", + "bookmark-dir-label": "", + "bookmark-dir-tooltip": "", + "change": "", + "reset-to-default": "", + "reset": "", + "save": "", + "media-issue-title": "", + "scrobble-issue-title": "" + }, + "manage-scrobble-errors": { + "description": "", + "filter-label": "", + "clear-errors": "", + "series-header": "", + "created-header": "", + "comment-header": "", + "edit-header": "", + "edit-item-alt": "" + }, + "default-date-pipe": { + "never": "" + }, + "manage-settings": { + "notice": "", + "restart-required": "", + "base-url-label": "", + "base-url-tooltip": "", + "ip-address-label": "", + "ip-address-tooltip": "", + "port-label": "", + "port-tooltip": "", + "backup-label": "", + "backup-tooltip": "", + "log-label": "", + "log-tooltip": "", + "logging-level-label": "", + "logging-level-tooltip": "", + "cache-size-label": "", + "cache-size-tooltip": "", + "on-deck-last-progress-label": "", + "on-deck-last-progress-tooltip": "", + "on-deck-last-chapter-add-label": "", + "on-deck-last-chapter-add-tooltip": "", + "allow-stats-label": "", + "allow-stats-tooltip-part-1": "", + "allow-stats-tooltip-part-2": "", + "send-data": "", + "opds-label": "", + "opds-tooltip": "", + "enable-opds": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "enable-folder-watching": "", + "reset-to-default": "", + "reset": "", + "save": "", + "cache-size-validation": "", + "field-required": "", + "max-logs-validation": "", + "min-logs-validation": "", + "min-days-validation": "", + "min-cache-validation": "", + "max-backup-validation": "", + "min-backup-validation": "", + "ip-address-validation": "", + "base-url-validation": "" + }, + "manage-system": { + "title": "", + "version-title": "", + "installId-title": "", + "more-info-title": "", + "home-page-title": "", + "wiki-title": "", + "discord-title": "", + "donations-title": "", + "source-title": "", + "feature-request-title": "" + }, + "manage-tasks-settings": { + "title": "", + "library-scan-label": "", + "library-scan-tooltip": "", + "library-database-backup-label": "", + "library-database-backup-tooltip": "", + "adhoc-tasks-title": "", + "job-title-header": "", + "description-header": "", + "action-header": "", + "reset-to-default": "", + "reset": "", + "save": "", + "recurring-tasks-title": "", + "last-executed-header": "", + "cron-header": "", + "convert-media-task": "", + "convert-media-task-desc": "", + "convert-media-success": "", + "bust-cache-task": "", + "bust-cache-task-desc": "", + "bust-cache-task-success": "", + "clear-reading-cache-task": "", + "clear-reading-cache-task-desc": "", + "clear-reading-cache-task-success": "", + "clean-up-want-to-read-task": "", + "clean-up-want-to-read-task-desc": "", + "clean-up-want-to-read-task-success": "", + "backup-database-task": "", + "backup-database-task-desc": "", + "backup-database-task-success": "", + "download-logs-task": "", + "download-logs-task-desc": "", + "analyze-files-task": "", + "analyze-files-task-desc": "", + "analyze-files-task-success": "", + "check-for-updates-task": "", + "check-for-updates-task-desc": "" + }, + "manage-users": { + "title": "", + "invite": "", + "you-alt": "", + "pending-title": "", + "delete-user-tooltip": "", + "delete-user-alt": "", + "edit-user-tooltip": "", + "edit-user-alt": "", + "resend-invite-tooltip": "", + "resend-invite-alt": "", + "setup-user-tooltip": "", + "setup-user-alt": "", + "change-password-tooltip": "", + "change-password-alt": "", + "resend": "", + "setup": "", + "last-active-title": "", + "roles-title": "", + "none": "", + "never": "", + "online-now-tooltip": "", + "sharing-title": "", + "no-data": "", + "loading": "" + }, + "edit-collection-tags": { + "title": "", + "required-field": "", + "save": "", + "close": "", + "cancel": "", + "general-tab": "", + "cover-image-tab": "", + "series-tab": "", + "name-label": "", + "name-validation": "", + "promote-label": "", + "promote-tooltip": "", + "summary-label": "", + "series-title": "", + "deselect-all": "", + "select-all": "" + }, + "library-detail": { + "library-tab": "", + "recommended-tab": "" + }, + "library-recommended": { + "no-data": "", + "more-in-genre": "", + "rediscover": "", + "highly-rated": "", + "quick-catchups": "", + "quick-reads": "", + "on-deck": "" + }, + "admin-dashboard": { + "title": "", + "general-tab": "", + "users-tab": "", + "libraries-tab": "", + "media-tab": "", + "logs-tab": "", + "email-tab": "", + "tasks-tab": "", + "statistics-tab": "", + "system-tab": "", + "kavita+-tab": "", + "kavita+-desc-part-1": "", + "kavita+-desc-part-2": "", + "kavita+-desc-part-3": "" + }, + "collection-detail": { + "no-data": "", + "no-data-filtered": "", + "title-alt": "" + }, + "all-collections": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "carousel-reel": { + "prev-items": "", + "next-items": "" + }, + "draggable-ordered-list": { + "instructions-alt": "", + "reorder-label": "", + "remove-item-alt": "" + }, + "reading-lists": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "reading-list-item": { + "remove": "", + "read": "" + }, + "reading-list-detail": { + "item-count": "", + "page-settings-title": "", + "remove-read": "", + "order-numbers-label": "", + "continue": "", + "read": "", + "read-options-alt": "", + "incognito-alt": "", + "no-data": "" + }, + "events-widget": { + "title-alt": "", + "dismiss-all": "", + "update-available": "", + "downloading-item": "", + "more-info": "", + "close": "", + "users-online-count": "", + "active-events-title": "", + "no-data": "" + }, + "shortcuts-modal": { + "title": "", + "close": "", + "prev-page": "", + "next-page": "", + "go-to": "", + "bookmark": "", + "double-click": "", + "close-reader": "", + "toggle-menu": "" + }, + "grouped-typeahead": { + "files": "", + "chapters": "", + "people": "", + "tags": "", + "genres": "", + "libraries": "", + "reading-lists": "", + "collections": "", + "close": "", + "loading": "" + }, + "nav-header": { + "skip-alt": "", + "search-series-alt": "", + "search-alt": "", + "promoted": "", + "no-data": "", + "scroll-to-top-alt": "", + "server-settings": "", + "settings": "", + "help": "", + "announcements": "", + "logout": "" + }, + "add-to-list-modal": { + "title": "", + "close": "", + "filter-label": "", + "promoted-alt": "", + "no-data": "", + "loading": "", + "reading-list-label": "", + "create": "" + }, + "edit-reading-list-modal": { + "title": "", + "general-tab": "", + "cover-image-tab": "", + "close": "", + "save": "", + "year-validation": "", + "month-validation": "", + "name-unique-validation": "", + "required-field": "", + "summary-label": "", + "year-label": "", + "month-label": "", + "ending-title": "", + "starting-title": "", + "promote-label": "", + "promote-tooltip": "" + }, + "import-cbl-modal": { + "close": "", + "title": "", + "import-description": "", + "validate-description": "", + "validate-warning": "", + "validate-no-issue": "", + "validate-no-issue-description": "", + "dry-run-description": "", + "prev": "", + "import": "", + "restart": "", + "next": "", + "import-step": "", + "validate-cbl-step": "", + "dry-run-step": "", + "final-import-step": "" + }, + "pdf-reader": { + "loading-message": "", + "incognito-mode": "", + "light-theme-alt": "", + "dark-theme-alt": "", + "close-reader-alt": "" + }, + "manga-reader": { + "back": "", + "save-globally": "", + "incognito-alt": "", + "incognito-title": "", + "shortcuts-menu-alt": "", + "prev-page-tooltip": "", + "next-page-tooltip": "", + "prev-chapter-tooltip": "", + "next-chapter-tooltip": "", + "first-page-tooltip": "", + "last-page-tooltip": "", + "left-to-right-alt": "", + "right-to-left-alt": "", + "reading-direction-tooltip": "", + "reading-mode-tooltip": "", + "collapse": "", + "fullscreen": "", + "settings-tooltip": "", + "image-splitting-label": "", + "image-scaling-label": "", + "height": "", + "width": "", + "original": "", + "auto-close-menu-label": "", + "swipe-enabled-label": "", + "enable-comic-book-label": "", + "brightness-label": "", + "first-time-reading-manga": "", + "layout-mode-switched": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "user-preferences-updated": "", + "emulate-comic-book-label": "" + }, + "metadata-filter": { + "filter-title": "", + "format-label": "", + "libraries-label": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "read-progress-label": "", + "unread": "", + "read": "", + "in-progress": "", + "rating-label": "", + "age-rating-label": "", + "language-label": "", + "publication-status-label": "", + "series-name-label": "", + "series-name-tooltip": "", + "release-label": "", + "min": "", + "max": "", + "sort-by-label": "", + "ascending-alt": "", + "descending-alt": "", + "reset": "", + "apply": "" + }, + "sort-field-pipe": { + "sort-name": "", + "created": "", + "last-modified": "", + "last-chapter-added": "", + "time-to-read": "", + "release-year": "" + }, + "edit-series-modal": { + "title": "", + "general-tab": "", + "metadata-tab": "", + "people-tab": "", + "web-links-tab": "", + "cover-image-tab": "", + "related-tab": "", + "info-tab": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "language-label": "", + "age-rating-label": "", + "publication-status-label": "", + "required-field": "", + "close": "", + "name-label": "", + "sort-name-label": "", + "localized-name-label": "", + "summary-label": "", + "release-year-label": "", + "web-link-description": "", + "web-link-label": "", + "add-link-alt": "", + "remove-link-alt": "", + "cover-image-description": "", + "save": "", + "field-locked-alt": "", + "info-title": "", + "library-title": "", + "format-title": "", + "created-title": "", + "last-read-title": "", + "last-added-title": "", + "last-scanned-title": "", + "folder-path-title": "", + "publication-status-title": "", + "total-pages-title": "", + "total-items-title": "", + "max-items-title": "", + "size-title": "", + "loading": "", + "added-title": "", + "last-modified-title": "", + "view-files": "", + "pages-title": "", + "chapter-title": "", + "volume-num": "", + "highest-count-tooltip": "", + "max-issue-tooltip": "" + }, + "day-breakdown": { + "title": "", + "x-axis-label": "", + "y-axis-label": "" + }, + "file-breakdown-stats": { + "format-title": "", + "format-tooltip": "", + "visualisation-label": "", + "data-table-label": "", + "extension-header": "", + "format-header": "", + "total-size-header": "", + "total-files-header": "", + "not-classified": "", + "total-file-size-title": "" + }, + "reading-activity": { + "title": "", + "legend-label": "", + "x-axis-label": "", + "y-axis-label": "", + "no-data": "", + "time-frame-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "manga-format-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "format-header": "", + "count-header": "" + }, + "publication-status-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "year-header": "", + "count-header": "" + }, + "server-stats": { + "total-series-label": "", + "total-series-tooltip": "", + "total-volumes-label": "", + "total-volumes-tooltip": "", + "total-files-label": "", + "total-files-tooltip": "", + "total-size-label": "", + "total-genres-label": "", + "total-genres-tooltip": "", + "total-tags-label": "", + "total-tags-tooltip": "", + "total-people-label": "", + "total-people-tooltip": "", + "total-read-time-label": "", + "total-read-time-tooltip": "", + "series": "", + "reads": "", + "release-years-title": "", + "most-active-users-title": "", + "popular-libraries-title": "", + "popular-series-title": "", + "recently-read-title": "", + "genre-count": "", + "tag-count": "", + "people-count": "", + "tags": "", + "people": "", + "genres": "" + }, + "errors": { + "series-doesnt-exist": "", + "collection-invalid-access": "", + "unknown-crit": "", + "user-not-auth": "", + "error-code": "", + "download": "", + "not-found": "", + "generic": "", + "rejected-cover-upload": "", + "invalid-confirmation-url": "", + "invalid-confirmation-email": "", + "invalid-password-reset-url": "" + }, + "toasts": { + "regen-cover": "", + "no-pages": "", + "download-in-progress": "", + "scan-queued": "", + "server-settings-updated": "", + "reset-ip-address": "", + "reset-base-url": "", + "unauthorized-1": "", + "unauthorized-2": "", + "no-updates": "", + "confirm-delete-user": "", + "user-deleted": "", + "email-sent-to-user": "", + "click-email-link": "", + "series-added-to-collection": "", + "no-series-collection-warning": "", + "collection-updated": "", + "reading-list-deleted": "", + "reading-list-updated": "", + "confirm-delete-reading-list": "", + "item-removed": "", + "nothing-to-remove": "", + "series-added-to-reading-list": "", + "volumes-added-to-reading-list": "", + "chapter-added-to-reading-list": "", + "multiple-added-to-reading-list": "", + "select-files-warning": "", + "reading-list-imported": "", + "incognito-off": "", + "email-service-reset": "", + "email-service-reachable": "", + "email-service-unresponsive": "", + "refresh-covers-queued": "", + "library-file-analysis-queued": "", + "entity-read": "", + "entity-unread": "", + "mark-read": "", + "mark-unread": "", + "series-removed-want-to-read": "", + "series-deleted": "", + "file-send-to": "", + "theme-missing": "", + "email-sent": "", + "k+-license-saved": "", + "k+-unlocked": "", + "k+-error": "", + "k+-delete-key": "", + "library-deleted": "", + "copied-to-clipboard": "", + "book-settings-info": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "load-next-chapter": "", + "load-prev-chapter": "", + "account-registration-complete": "", + "account-migration-complete": "", + "password-reset": "", + "password-updated": "", + "forced-scan-queued": "", + "library-created": "", + "anilist-token-updated": "", + "age-restriction-updated": "", + "email-sent-to-no-existing": "", + "email-sent-to": "", + "change-email-private": "", + "device-updated": "", + "device-created": "", + "confirm-regen-covers": "", + "alert-long-running": "", + "confirm-delete-multiple-series": "", + "confirm-delete-series": "", + "alert-bad-theme": "", + "confirm-library-delete": "", + "confirm-library-type-change": "", + "confirm-download-size": "" + }, + "actionable": { + "scan-library": "", + "refresh-covers": "", + "analyze-files": "", + "settings": "", + "edit": "", + "mark-as-read": "", + "mark-as-unread": "", + "scan-series": "", + "add-to": "", + "add-to-want-to-read": "", + "remove-from-want-to-read": "", + "remove-from-on-deck": "", + "others": "", + "add-to-reading-list": "", + "add-to-collection": "", + "send-to": "", + "delete": "", + "download": "", + "read-incognito": "", + "details": "", + "view-series": "", + "clear": "", + "import-cbl": "", + "read": "" + }, + "preferences": { + "left-to-right": "", + "right-to-left": "", + "horizontal": "", + "vertical": "", + "automatic": "", + "fit-to-height": "", + "fit-to-width": "", + "original": "", + "fit-to-screen": "", + "no-split": "", + "webtoon": "", + "single": "", + "double": "", + "double-manga": "", + "scroll": "", + "1-column": "", + "2-column": "", + "cards": "", + "list": "", + "up-to-down": "" + }, + "validation": { + "required-field": "", + "valid-email": "", + "password-validation": "" + }, + "entity-type": { + "volume": "", + "chapter": "", + "series": "", + "bookmark": "", + "logs": "" + }, + "common": { + "reset-to-default": "", + "close": "", + "cancel": "", + "create": "", + "save": "", + "reset": "", + "add": "", + "apply": "", + "delete": "", + "edit": "", + "help": "", + "submit": "", + "email": "", + "read": "", + "loading": "", + "username": "", + "password": "", + "promoted": "", + "select-all": "", + "deselect-all": "", + "series-count": "", + "item-count": "", + "book-num": "", + "issue-hash-num": "", + "issue-num": "", + "chapter-num": "", + "volume-num": "" + } +} diff --git a/UI/Web/src/assets/langs/pt.json b/UI/Web/src/assets/langs/pt.json index 0b44ab5784..4d35c11bb5 100644 --- a/UI/Web/src/assets/langs/pt.json +++ b/UI/Web/src/assets/langs/pt.json @@ -11,7 +11,7 @@ "no-libraries": "Não existem bibliotecas configuradas. Configure algumas em", "server-settings-link": "Definições do servidor", "not-granted": "Não lhe foi atribuído acesso a nenhuma biblioteca.", - "on-deck-title": "", + "on-deck-title": "Continue a Ler", "recently-updated-title": "Séries Atualizadas Recentemente", "recently-added-title": "Séries Adicionadas Recentemente" }, @@ -27,8 +27,8 @@ "update": "Atualizar" }, "user-scrobble-history": { - "title": "", - "description": "", + "title": "Histórico de Scrobble", + "description": "Aqui você encontrará todos os eventos scrobble vinculados à sua conta. Para que os eventos existam, você deve ter um provedor scrobble ativo configurado. Todos os eventos processados serão apagados após um mês. Se houver eventos não processados, é provável que eles não possam formar correspondências upstream. Entre em contato com seu administrador para corrigi-los.", "filter-label": "Filtro", "created-header": "Criado", "last-modified-header": "Última alteração", @@ -44,18 +44,18 @@ "not-processed": "Não Processado" }, "scrobble-event-type-pipe": { - "chapter-read": "", + "chapter-read": "Leitura Efetuada", "score-updated": "Atualização de Classificação", - "want-to-read-add": "", - "want-to-read-remove": "", + "want-to-read-add": "Leituras Desejadas: Adicionar", + "want-to-read-remove": "Leituras Desejadas: Eliminar", "review": "Atualização de crítica" }, "spoiler": { - "click-to-show": "" + "click-to-show": "Spoiler, clique para ver" }, "review-series-modal": { "title": "Editar Crítica", - "tagline-label": "", + "tagline-label": "Resumo", "review-label": "Crítica", "close": "{{common.close}}", "save": "{{common.save}}" @@ -79,7 +79,7 @@ "no-items-filtered": "Não existem itens para o filtro atual." }, "user-preferences": { - "title": "", + "title": "Painel do Utilizador", "pref-description": "Estas definições globais estão ligadas à sua conta.", "account-tab": "Conta", "preferences-tab": "Preferências", @@ -87,13 +87,13 @@ "theme-tab": "Tema", "devices-tab": "Dispositivos", "stats-tab": "Estatísticas", - "scrobbling-tab": "", + "scrobbling-tab": "Scrobbling", "success-toast": "Preferências de utilizador atualizadas", "global-settings-title": "Definições globais", - "page-layout-mode-label": "", - "page-layout-mode-tooltip": "", + "page-layout-mode-label": "Modo de Exibição das Páginas", + "page-layout-mode-tooltip": "Mostrar itens como cartões ou vista de lista na página Detalhes da Série.", "locale-label": "", - "locale-tooltip": "O idiota que o Kavita deve usar", + "locale-tooltip": "O idioma que o Kavita deve usar", "blur-unread-summaries-label": "Ofuscar sumários não lidos", "blur-unread-summaries-tooltip": "Ofusca o sumário em volumes ou capítulos que não tenham leitura em curso (para evitar spoilers)", "prompt-on-download-label": "Aviso nos Downloads", @@ -101,40 +101,40 @@ "disable-animations-label": "Desabilitar animações", "disable-animations-tooltip": "Desligar animações no site. Útil para leitores e-ink.", "collapse-series-relationships-label": "Colapsar Relações de Séries", - "collapse-series-relationships-tooltip": "", + "collapse-series-relationships-tooltip": "O Kavita deve mostrar Séries que não tenham relações ou é o pai/prequela", "share-series-reviews-label": "Partilhar Críticas de Séries", "share-series-reviews-tooltip": "As suas críticas de Séries devem ser incluídas para outros utilizadores pelo Kavita", - "image-reader-settings-title": "", + "image-reader-settings-title": "Leitor de Imagens", "reading-direction-label": "Direção de Leitura", - "reading-direction-tooltip": "", + "reading-direction-tooltip": "Direção a clicar para ir para a próxima página. Direita para a Esquerda significa que se clica no lado esquerdo do ecrã para ir para a página seguinte.", "scaling-option-label": "Opções de Dimensionamento", "scaling-option-tooltip": "Como adaptar a dimensão da imagem ao seu ecrã.", - "page-splitting-label": "", - "page-splitting-tooltip": "", + "page-splitting-label": "Separação de Páginas", + "page-splitting-tooltip": "Como separar uma imagem que ocupa a largura completa (p.e., as imagens esquerda e direita estão juntas numa só)", "reading-mode-label": "Modo de Leitura", - "layout-mode-label": "", - "layout-mode-tooltip": "", + "layout-mode-label": "Modo de Exibição", + "layout-mode-tooltip": "Mostrar uma única imagem no ecrã ou mostrar duas imagens lado a lado", "background-color-label": "Cor de Fundo", "auto-close-menu-label": "Fechar Menu Automaticamente", - "show-screen-hints-label": "", - "emulate-comic-book-label": "", - "swipe-to-paginate-label": "", - "book-reader-settings-title": "", - "tap-to-paginate-label": "", - "tap-to-paginate-tooltip": "", + "show-screen-hints-label": "Mostrar Dicas no Ecrã", + "emulate-comic-book-label": "Emular livro de BD", + "swipe-to-paginate-label": "Deslize para Paginar", + "book-reader-settings-title": "Leitor de Livros", + "tap-to-paginate-label": "Toque para Paginar", + "tap-to-paginate-tooltip": "Se é permitido tocar nos lados do ecrã do leitor de livros para ir para a próxima/anterior página", "immersive-mode-label": "Modo Imersivo", - "immersive-mode-tooltip": "", + "immersive-mode-tooltip": "O menu será escondido com um clique no documento e o toque para paginar ficará ativo", "reading-direction-book-label": "Direção de Leitura", - "reading-direction-book-tooltip": "", - "font-family-label": "", - "font-family-tooltip": "", - "writing-style-label": "", + "reading-direction-book-tooltip": "Direção a clicar para ir para a página seguinte. Direita para Esquerda significa que se clica no lado esquerdo do ecrã para ir para a página seguinte.", + "font-family-label": "Família de Fonte", + "font-family-tooltip": "Família de fonte a carregar. Por defeito será carregado a fonte do livro", + "writing-style-label": "Estilo de Escrita", "writing-style-tooltip": "Muda a direção do texto. Horizontal é da esquerda para direita, vertical é do topo para o fundo.", - "layout-mode-book-label": "", - "layout-mode-book-tooltip": "", + "layout-mode-book-label": "Modo de Exibição", + "layout-mode-book-tooltip": "Como o conteúdo deve ser exibido. A opção Scroll exibe de acordo com o definido no arquivo. As opções 1 ou 2 Colunas, ajustam à altura do dispositivo e mostram 1 ou 2 colunas de texto por página", "color-theme-book-label": "Tema de Cor", "color-theme-book-tooltip": "Que tema de cores aplicar ao conteúdo e menu do leitor de livros", - "font-size-book-label": "", + "font-size-book-label": "Tamanho de Fonte", "line-height-book-label": "Espaçamento Entre Linhas", "line-height-book-tooltip": "Quanto espaçamento entre as linhas do livro", "margin-book-label": "Margem", @@ -147,20 +147,20 @@ "save": "{{common.save}}" }, "user-holds": { - "title": "", - "description": "" + "title": "Retenções de Scrobble", + "description": "Esta é uma lista de Séries, gerida por utilizador, que não será scrobbled para os fornecedores upstream. Pode eliminar uma série a qualquer momento e próximo evento de scrobble (progresso de leitura, classificação, estado de leituras futuras) irá despoletar eventos." }, "theme-manager": { "title": "Gestor de Temas", "looking-for-theme": "À procura de um tema 'light' ou e-ink? Pode encontrar alguns temas customizados no nosso ", "looking-for-theme-continued": "github de temas.", - "scan": "", + "scan": "Analisar", "site-themes": "Temas do Site", "set-default": "Tema Por Defeito", "apply": "{{common.apply}}", - "applied": "", + "applied": "Aplicado", "updated-toastr": "O tema por defeito do site foi atualizado para {{name}}", - "scan-queued": "" + "scan-queued": "Foi agendado um scan the temas do site" }, "theme": { "theme-dark": "Escuro", @@ -169,13 +169,13 @@ "theme-white": "Branco" }, "restriction-selector": { - "title": "", + "title": "Restrição de Faixa Etária", "description": "Quando selecionado, todas as séries e listas de leitura, que tenham pelo menos um elemento que seja superior à restrição selecionada, serão removidas dos resultados.", "not-applicable-for-admins": "Não aplicável a administradores.", "age-rating-label": "Classificação etária", - "no-restriction": "", - "include-unknowns-label": "", - "include-unknowns-tooltip": "" + "no-restriction": "Sem Restrições", + "include-unknowns-label": "Incluir Desconhecidos", + "include-unknowns-tooltip": "Quando ativado, os Desconhecidos serão permitidos independentemente da Restrição de Faixa Etária. Com esta opção é possível que elementos que não estejam devidamente categorizados sejam mostrados a utilizadores com restrições etárias definidas." }, "site-theme-provider-pipe": { "system": "Sistema", @@ -195,8 +195,8 @@ "edit-device": { "device-name-label": "Nome do Dispositivo", "email-label": "{{common.email}}", - "email-tooltip": "", - "device-platform-label": "", + "email-tooltip": "Este email será usado para aceitar o ficheiro através da funcionalidade Enviar Para", + "device-platform-label": "Plataforma do Dispositivo", "save": "{{common.save}}", "required-field": "{{validation.required-field}}", "valid-email": "{{validation.valid-email}}" @@ -243,17 +243,18 @@ "regen-warning": "Se recriar a chave da API, os clientes de terceiros existentes deixarão de ter acesso.", "no-key": "ERRO - CHAVE NÃO DEFINIDA", "confirm-reset": "Isto irá invalidar todas as configurações OPDS. Tem a certeza que deseja continuar?", - "key-reset": "Repor chave da API" + "key-reset": "Repor chave da API", + "show": "Mostrar" }, "scrobbling-providers": { - "title": "", + "title": "Fornecedores de Scrobbling", "requires": "Esta funcionalidade requer uma licença {{product}} ativa", "token-expired": "Token Expirado", "no-token-set": "Token Não Definido", "token-set": "Token Definido", "generate": "Gerar", - "instructions": "", - "token-input-label": "", + "instructions": "Os utilizadores pela primeira vez devem clicar em \"{{scrobbling-providers.generate}}\" para que seja permitido ao Kavita+ comunicar com o {{service}}. Depois do programa estar autorizado, copie e cole o token no campo abaixo. O token pode ser recriado quando quiser.", + "token-input-label": "Colocar Token do {{service}} Aqui", "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", "save": "{{common.save}}" @@ -264,7 +265,7 @@ "loading": "{{common.loading}}", "add-item": "Adicionar {{item}}…", "no-data": "Sem dados", - "add-custom-item": "" + "add-custom-item": ", digite para adicionar um item personalizado" }, "generic-list-modal": { "close": "{{common.close}}", @@ -277,7 +278,7 @@ "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", "total-words-read-label": "Palavras Totais Lidas", "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", - "time-spent-reading-label": "", + "time-spent-reading-label": "Tempo Total a Ler", "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", "chapters-read-label": "Capítulos Lidos", "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", @@ -291,7 +292,7 @@ }, "top-readers": { "title": "Top de Leitores", - "time-selection-label": "", + "time-selection-label": "Período de Tempo", "comics-label": "BDs: {{value}} hrs", "manga-label": "Manga: {{value}} hrs", "books-label": "Livros: {{value}} hrs", @@ -303,14 +304,14 @@ "all-time": "{{time-periods.all-time}}" }, "role-selector": { - "title": "" + "title": "Funções" }, "directory-picker": { "title": "Escolha uma Pasta", "close": "{{common.close}}", "path-label": "Caminho", "path-placeholder": "Comece a escrever ou escolha um caminho", - "instructions": "", + "instructions": "Selecione uma pasta para ver a barra de rastreamento. Não vê a sua diretoria? Tente abrir a raíz / primeiro.", "type-header": "Tipo", "name-header": "Nome", "cancel": "{{common.cancel}}", @@ -333,10 +334,10 @@ "last-30-days": "Últimos 30 Dias", "last-90-days": "Últimos 90 Dias", "last-year": "Último Ano", - "all-time": "" + "all-time": "Todo o Tempo" }, "device-platform-pipe": { - "custom": "" + "custom": "Customizado" }, "day-of-week-pipe": { "monday": "Segunda-feira", @@ -353,15 +354,15 @@ "failure": "Falha" }, "cbl-conflict-reason-pipe": { - "all-series-missing": "", + "all-series-missing": "A sua conta não tem acesso a todas as séries na lista ou o Kavita não tem nada na lista.", "chapter-missing": "{{series}}: Capítulo {{chapter}} não existe no Kavita. Este item será ignorado.", "empty-file": "O ficheiro cbl está vazio, não irá ser feito nada.", - "name-conflict": "", + "name-conflict": "Já existe uma lista de leitura {{readingListName}} na sua conta correspondente ao ficheiro cbl.", "series-collision": "A série, {{seriesLink}}, colide com outra série com o mesmo nome noutra biblioteca.", "series-missing": "A série, {{series}}, não existe no Kavita ou a sua conta não tem permissões suficientes. Todos os itens desta série serão ignorados na importação.", "volume-missing": "{{series}}: O volume {{volume}} não existe no Kavita ou a sua conta não tem as permissões suficientes. Todos os itens com este número de volume serão ignorados.", - "all-chapter-missing": "", - "invalid-file": "", + "all-chapter-missing": "Alguns capítulos não correspondem a Capítulos no Kavita.", + "invalid-file": "O ficheiro está corrompido ou não tem as tags/especificação esperadas.", "success": "{{series}} volume {{volume}} capítulo {{chapter}} mapeado com sucesso." }, "time-duration-pipe": { @@ -387,7 +388,7 @@ }, "relationship-pipe": { "adaptation": "Adaptação", - "alternative-setting": "", + "alternative-setting": "Cenário Alternativo", "alternative-version": "Versão Alternativa", "character": "Personagem", "contains": "Contém", @@ -395,9 +396,9 @@ "other": "Outra", "prequel": "Prequela", "sequel": "Sequela", - "side-story": "", - "spin-off": "", - "parent": "", + "side-story": "História Paralela", + "spin-off": "Spin-Off", + "parent": "Origem", "edition": "Edição" }, "publication-status-pipe": { @@ -410,13 +411,13 @@ "person-role-pipe": { "artist": "Artista", "character": "Personagem", - "colorist": "", - "cover-artist": "", + "colorist": "Colorista", + "cover-artist": "Artista de Capa", "editor": "Editor", - "inker": "", - "letterer": "", - "penciller": "", - "publisher": "Publicador", + "inker": "Arte-Finalista", + "letterer": "Letrista", + "penciller": "Desenhista", + "publisher": "Editora", "writer": "Escritor", "other": "Outro" }, @@ -429,20 +430,20 @@ }, "library-type-pipe": { "book": "Livro", - "comic": "", + "comic": "BD", "manga": "Manga" }, "age-rating-pipe": { "unknown": "Desconhecido", - "early-childhood": "", + "early-childhood": "Pré-Escolar", "adults-only": "Adultos 18+", "everyone": "Todos", "everyone-10-plus": "Todos 10+", "g": "G", "kids-to-adults": "Crianças a Adultos", - "mature": "", + "mature": "Maiores de 18", "ma15-plus": "MA15+", - "mature-17-plus": "", + "mature-17-plus": "Maiores de 17", "rating-pending": "Classificação Pendente", "teen": "Adolescente", "x18-plus": "X18+", @@ -452,7 +453,7 @@ }, "reset-password": { "title": "Repor Palavra Passe", - "description": "", + "description": "Introduza o endereço de email da sua conta. O Kavita irá enviar um email se encontrar um válido em ficheiro, caso contrário peça ao administrador pelo link dos logs.", "email-label": "{{common.email}}", "required-field": "{{validation.required-field}}", "valid-email": "{{validation.valid-email}}", @@ -467,30 +468,31 @@ "save": "{{common.save}}" }, "all-series": { - "series-count": "{{common.series-count}}" + "series-count": "{{common.series-count}}", + "title": "Todas as Séries" }, "announcements": { - "title": "" + "title": "Anúncios" }, "changelog": { "installed": "Instalado", - "download": "", + "download": "Descarregar", "published-label": "Publicado: ", "available": "Disponível", - "description": "", - "description-continued": "" + "description": "Se não vir uma etiqueta {{installed}}", + "description-continued": ", está numa versão em desenvolvimento. Apenas as versões principais aparecerão como disponíveis." }, "invite-user": { "title": "Convidar Utilizador", "close": "{{common.close}}", - "description": "", + "description": "Convide um utilizador para o servidor. Introduza o endereço de email e nós enviaremos um email para criar a conta. Se não quiser usar o nosso serviço de email, pode usar o seu próprio serviço ou usar um email inexistente (a funcionalidade Esqueci o Utilizador deixará de funcionar). De qualquer maneira será disponibilizado um link para poder configurar a conta manualmente.", "email": "{{common.email}}", "required-field": "{{common.required-field}}", "setup-user-title": "Utilizador convidado", - "setup-user-description": "", - "setup-user-account": "", - "setup-user-account-tooltip": "", - "invite-url-label": "", + "setup-user-description": "Pode usar o link abaixo, ou usar o botão copiar, para configurar a conta de utilizador. Poderá ser necessário terminar a sessão antes de o usar o link para registar o novo utilizador. Se o seu servidor for acessível do exterior, será enviado um email para o utilizador e os links podem ser usados para concluir a configuração da conta.", + "setup-user-account": "Configurar conta de utilizador", + "setup-user-account-tooltip": "Copie este URL e cole-o numa nova aba. Poderá ser necessário terminar a sessão.", + "invite-url-label": "Url de Convite", "invite": "Convite", "inviting": "A convidar…", "cancel": "{{common.cancel}}" @@ -515,7 +517,7 @@ "license-valid": "Licença é Válida", "license-not-valid": "Licença Inválida", "loading": "{{common.loading}}", - "activate-description": "", + "activate-description": "Introduza a Licença e o Email usados para registar com o Stripe", "activate-license-label": "Licença", "activate-email-label": "{{common.email}}", "activate-delete": "Eliminar", @@ -538,1145 +540,1190 @@ "prev-page": "Página Anterior", "next-page": "Página Seguinte", "prev-chapter": "Capítulo/Volume Anterior", - "next-chapter": "", - "skip-header": "", - "virtual-pages": "", - "settings-header": "", - "table-of-contents-header": "", - "bookmarks-header": "", - "toc-header": "", - "loading-book": "", - "go-back": "", - "incognito-mode-alt": "", - "incognito-mode-label": "", - "next": "", - "previous": "" + "next-chapter": "Capítulo/Volume Seguinte", + "skip-header": "Saltar para o conteúdo principal", + "virtual-pages": "páginas virtuais", + "settings-header": "Definições", + "table-of-contents-header": "Índice", + "bookmarks-header": "Marcadores", + "toc-header": "ToC", + "loading-book": "A carregar livro…", + "go-back": "Voltar Atrás", + "incognito-mode-alt": "Modo incógnito ativado. Pressione novamente para desligar.", + "incognito-mode-label": "Modo Incógnito", + "next": "Seguinte", + "previous": "Anterior", + "go-to-page-prompt": "Existem {{totalPages}} páginas. Para que página deseja ir?" }, "personal-table-of-contents": { - "no-data": "", - "page": "", - "delete": "" + "no-data": "Ainda não existem marcadores", + "page": "Página {{value}}", + "delete": "Eliminar {{bookmarkName}}" }, "confirm-email": { - "title": "", - "description": "", - "error-label": "", - "username-label": "", - "password-label": "", - "email-label": "", - "required-field": "", - "valid-email": "", - "password-validation": "", - "register": "" + "title": "Registar", + "description": "Complete o formulário para terminar o seu registo", + "error-label": "Erros: ", + "username-label": "{{common.username}}", + "password-label": "{{common.password}}", + "email-label": "{{common.email}}", + "required-field": "{{common.required-field}}", + "valid-email": "{{common.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "Registar" }, "confirm-email-change": { - "title": "", - "non-confirm-description": "", - "confirm-description": "", - "success": "" + "title": "Validar Alteração de Email", + "non-confirm-description": "Por favor aguarde enquanto a alteração de email é validada.", + "confirm-description": "O seu email foi validado e já está atualizado no Kavita. Irá ser redirecionado para o login.", + "success": "Sucesso!" }, "confirm-reset-password": { - "title": "", - "description": "", - "password-label": "", - "required-field": "", - "submit": "", - "password-validation": "" + "title": "Repor Palavra Passe", + "description": "Introduza a nova palavra passe", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}" }, "register": { - "title": "", - "description": "", - "username-label": "", - "email-label": "", - "email-tooltip": "", - "password-label": "", - "required-field": "", - "valid-email": "", - "password-validation": "", - "register": "" + "title": "Registar", + "description": "Complete o formulário para registar uma conta de administrador", + "username-label": "{{common.username}}", + "email-label": "{{common.email}}", + "email-tooltip": "O email não tem de ser um endereço real, mas é usado para recuperar as palavras passe esquecidas. O email não é enviado para fora do servidor a não ser que a recuperação de palavra passe seja feita com um serviço de email próprio.", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "Registar" }, "series-detail": { - "page-settings-title": "", - "close": "", - "layout-mode-label": "", - "layout-mode-option-card": "", - "layout-mode-option-list": "", - "continue-from": "", - "read": "", - "continue": "", - "read-options-alt": "", - "incognito": "", - "remove-from-want-to-read": "", - "add-to-want-to-read": "", - "edit-series-alt": "", - "download-series--tooltip": "", - "downloading-status": "", - "user-reviews-alt": "", - "storyline-tab": "", - "books-tab": "", - "volumes-tab": "", - "specials-tab": "", - "related-tab": "", - "recommendations-tab": "", - "send-to": "", - "no-pages": "", - "no-chapters": "", - "cover-change": "" + "page-settings-title": "Definições de Página", + "close": "{{common.close}}", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-option-card": "Cartão", + "layout-mode-option-list": "Lista", + "continue-from": "Continuar {{title}}", + "read": "{{common.read}}", + "continue": "Continuar", + "read-options-alt": "Opções de leitura", + "incognito": "Incógnito", + "remove-from-want-to-read": "Eliminar de Leituras Futuras", + "add-to-want-to-read": "Adicionar a Leituras Futuras", + "edit-series-alt": "Editar Informação da Série", + "download-series--tooltip": "Descarregar Série", + "downloading-status": "A Descarregar…", + "user-reviews-alt": "Críticas de Utilizadores", + "storyline-tab": "Enredo", + "books-tab": "Livros", + "volumes-tab": "Volumes", + "specials-tab": "Especiais", + "related-tab": "Relacionados", + "recommendations-tab": "Recomendações", + "send-to": "Ficheiro enviado por email para {{deviceName}}", + "no-pages": "{{toasts.no-pages}}", + "no-chapters": "Não existem capítulos neste volume. Impossível ler.", + "cover-change": "Pode levar até um minuto para a imagem ser refrescada pelo browser. Até isso acontecer, a imagem antiga será mostrada nalgumas páginas." }, "series-metadata-detail": { - "links-title": "", - "genres-title": "", - "tags-title": "", - "collections-title": "", - "reading-lists-title": "", - "writers-title": "", - "cover-artists-title": "", - "characters-title": "", - "colorists-title": "", - "editors-title": "", - "inkers-title": "", - "letterers-title": "", - "translators-title": "", - "pencillers-title": "", - "publishers-title": "", - "promoted": "", - "see-more": "", - "see-less": "" + "links-title": "Links", + "genres-title": "Géneros", + "tags-title": "Tags", + "collections-title": "{{side-nav.collections}}", + "reading-lists-title": "{{side-nav.reading-lists}}", + "writers-title": "Escritores", + "cover-artists-title": "Artistas de Capa", + "characters-title": "Personagens", + "colorists-title": "Coloristas", + "editors-title": "Editores", + "inkers-title": "Arte-Finalistas", + "letterers-title": "Desenhadores de letras", + "translators-title": "Tradutores", + "pencillers-title": "Desenhador", + "publishers-title": "Editoras", + "promoted": "{{common.promoted}}", + "see-more": "Ver Mais", + "see-less": "Ver Menos" }, "badge-expander": { - "more-items": "" + "more-items": "e mais {{count}}" }, "read-more": { - "read-more": "", - "read-less": "" + "read-more": "Ler Mais", + "read-less": "Ler Menos" }, "update-notification-modal": { - "title": "", - "close": "", - "help": "", - "download": "" + "title": "Nova Atualização Disponível!", + "close": "{{common.close}}", + "help": "Como Atualizar", + "download": "Descarregar" }, "side-nav-companion-bar": { - "page-settings-title": "", - "open-filter-and-sort": "", - "close-filter-and-sort": "", - "filter-and-sort-alt": "" + "page-settings-title": "{{series-detail.page-settings-title}}", + "open-filter-and-sort": "Abrir Filtragem e Ordenação", + "close-filter-and-sort": "Fechar Filtragem e Ordenação", + "filter-and-sort-alt": "Ordenar / Filtrar" }, "side-nav": { - "home": "", - "want-to-read": "", - "collections": "", - "reading-lists": "", - "bookmarks": "", - "filter-label": "", - "all-series": "", - "clear": "", - "donate": "" + "home": "Início", + "want-to-read": "Leituras Futuras", + "collections": "Coleções", + "reading-lists": "Listas de Leitura", + "bookmarks": "Marcadores", + "filter-label": "Filtrar", + "all-series": "Todas as Séries", + "clear": "Limpar", + "donate": "Donativo" }, "library-settings-modal": { - "close": "", - "edit-title": "", - "add-title": "", - "general-tab": "", - "folder-tab": "", - "cover-tab": "", - "advanced-tab": "", - "name-label": "", - "library-name-unique": "", - "last-scanned-label": "", - "type-label": "", - "type-tooltip": "", - "folder-description": "", - "browse": "", - "help-us-part-1": "", - "help-us-part-2": "", - "help-us-part-3": "", - "naming-conventions-part-1": "", - "naming-conventions-part-2": "", - "naming-conventions-part-3": "", - "cover-description": "", - "cover-description-extra": "", - "manage-collection-label": "", - "manage-collection-tooltip": "", - "manage-reading-list-label": "", - "manage-reading-list-tooltip": "", - "allow-scrobbling-label": "", - "allow-scrobbling-tooltip": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "include-in-dashboard-label": "", + "close": "{{common.close}}", + "edit-title": "Editar {{name}}", + "add-title": "Adicionar Biblioteca", + "general-tab": "Geral", + "folder-tab": "Pasta", + "cover-tab": "Capa", + "advanced-tab": "Avançado", + "name-label": "Nome", + "library-name-unique": "O nome da biblioteca tem de ser único", + "last-scanned-label": "Último Scan:", + "type-label": "Tipo", + "type-tooltip": "O tipo de biblioteca indica como os nomes dos ficheiros são processados e se o UI mostra Capítulos (Manga) vs Números (BDs). Os livros funcionam como a Manga mas têm nomes diferentes no UI.", + "folder-description": "Adicione pastas à sua biblioteca", + "browse": "Procure as Pastas Que Pretende Adicionar", + "help-us-part-1": "Ajude-nos e siga ", + "help-us-part-2": "o nosso guia", + "help-us-part-3": "para nomear e organizar os seus ficheiros.", + "naming-conventions-part-1": "O Kavita tem ", + "naming-conventions-part-2": "requisitos de pastas.", + "naming-conventions-part-3": "Veja este link para confirmar que está a seguir as convenções, caso contrário os ficheiros podem não ser detetados pelo scan.", + "cover-description": "As imagens para ícones customizados da biblioteca são opcionais", + "cover-description-extra": "A imagem da biblioteca não deve ser grande. Tente usar um ficheiro pequeno, p.e., de tamaho 32x32. O Kavita não valida o tamanho.", + "manage-collection-label": "Gerir Coleções", + "manage-collection-tooltip": "O Kavita deve criar Coleções a partir da tag SeriesGroup que se encontra nos ficheiros ComicInfo.xml/opf", + "manage-reading-list-label": "Gerir Listas de Leitura", + "manage-reading-list-tooltip": "O Kavita deve criar Listas de Leitura a partir das tags StoryArc/StoryArcNumber e AlternativeSeries/AlternativeCount encontradas nos ficheiros ComicInfo.xml/opf", + "allow-scrobbling-label": "Permitir Scrobbling", + "allow-scrobbling-tooltip": "Se o Kavita deve fazer scrobble de eventos de leitura, estado de leituras futuras, classificações, e críticas para os fornecedores configurados. Isto apenas ocorrerá se o servidor tiver uma Subscrição Kavita+ ativa.", + "folder-watching-label": "Monitorizar Pastas", + "folder-watching-tooltip": "Sobrepor a configuração de monitorização de pastas para esta biblioteca. Se desligada, a monitorização de pastas não correrá nas pastas desta biblioteca. Se houver pastas partilhadas entre bibliotecas, então a monitorização poderá correr na mesma.", + "include-in-dashboard-label": "Incluir no Painel", "include-in-dashboard-tooltip": "", - "include-in-recommendation-label": "", - "include-in-recommendation-tooltip": "", - "include-in-search-label": "", - "include-in-search-tooltip": "", - "force-scan": "", + "include-in-recommendation-label": "Incluir nas Recomendações", + "include-in-recommendation-tooltip": "Indica se as séries da biblioteca devem ser incluídas na página das Recomendações.", + "include-in-search-label": "Incluir na Pesquisa", + "include-in-search-tooltip": "Se as séries e informação derivada (género, pessoas, ficheiros) da biblioteca devem ser incluídas em resultados de pesquisas.", + "force-scan": "Forçar Scan", "force-scan-tooltip": "", - "reset": "", - "cancel": "", - "next": "", - "save": "", - "required-field": "" + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "next": "Seguinte", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}" }, "reader-settings": { - "general-settings-title": "", - "font-family-label": "", - "font-size-label": "", - "line-spacing-label": "", - "margin-label": "", - "reset-to-defaults": "", - "reader-settings-title": "", - "reading-direction-label": "", - "right-to-left": "", - "left-to-right": "", - "horizontal": "", - "vertical": "", - "writing-style-label": "", - "writing-style-tooltip": "", + "general-settings-title": "Definições Gerais", + "font-family-label": "{{user-preferences.font-family-label}}", + "font-size-label": "{{user-preferences.font-size-book-label}}", + "line-spacing-label": "{{user-preferences.line-height-book-label}}", + "margin-label": "{{user-preferences.margin-book-label}}", + "reset-to-defaults": "Repor Definições por Defeito", + "reader-settings-title": "Definições do Leitor", + "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "right-to-left": "Direita para a Esquerda", + "left-to-right": "Esquerda para a Direita", + "horizontal": "Horizontal", + "vertical": "Vertical", + "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-tooltip": "Altera a direção do texto. Horizontal é da esquerda para direita, veritcal é do topo para o fundo.", "tap-to-paginate-label": "", - "tap-to-paginate-tooltip": "", - "on": "", - "off": "", - "immersive-mode-label": "", + "tap-to-paginate-tooltip": "Clicar as extremidades do ecrã para paginar", + "on": "Ligado", + "off": "Desligado", + "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", "immersive-mode-tooltip": "", - "fullscreen-label": "", - "fullscreen-tooltip": "", - "exit": "", - "enter": "", - "layout-mode-label": "", + "fullscreen-label": "Ecrã Completo", + "fullscreen-tooltip": "Colocar o leitor em modo ecrã completo", + "exit": "Sair", + "enter": "Entrar", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", "layout-mode-tooltip": "", "layout-mode-option-scroll": "", - "layout-mode-option-1col": "", - "layout-mode-option-2col": "", - "color-theme-title": "", - "theme-dark": "", - "theme-black": "", - "theme-white": "", - "theme-paper": "" + "layout-mode-option-1col": "1 Coluna", + "layout-mode-option-2col": "2 Colunas", + "color-theme-title": "Tema de Cores", + "theme-dark": "Escuro", + "theme-black": "Preto", + "theme-white": "Branco", + "theme-paper": "Papel" }, "table-of-contents": { - "no-data": "" + "no-data": "Este livro não tem um Indíce definido nos metadados ou um ficheiro toc" }, "bookmarks": { - "title": "", - "series-count": "", - "no-data": "", - "no-data-2": "", - "confirm-delete": "", - "confirm-single-delete": "", - "delete-success": "", - "delete-single-success": "" + "title": "{{side-nav.bookmarks}}", + "series-count": "{{common.series-count}}", + "no-data": "Não existem marcadores. Tente criar", + "no-data-2": "um.", + "confirm-delete": "Tem a certeza que quer limpar todos os marcadores de múltiplas séries? Este passo é irreversível.", + "confirm-single-delete": "Tem a certeza que quer limpar todos os marcadores para {{seriesName}}? Este passo é irreversível.", + "delete-success": "Os marcadores foram eliminados", + "delete-single-success": "Os marcadores de {{seriesName}} foram eliminados" }, "bulk-operations": { - "title": "", - "items-selected": "", - "mark-as-unread": "", - "mark-as-read": "", - "deselect-all": "" + "title": "Operações em Massa", + "items-selected": "{{num}} itens selecionados", + "mark-as-unread": "Marcar como Não Lido", + "mark-as-read": "Marcar como Lido", + "deselect-all": "{{common.deselect-all}}" }, "card-detail-drawer": { - "general-tab": "", - "metadata-tab": "", - "cover-tab": "", - "info-tab": "", - "no-summary": "", - "writers-title": "", - "genres-title": "", - "publishers-title": "", - "tags-title": "", - "not-defined": "", - "read": "", - "unread": "", - "files": "", - "pages": "", - "added": "", - "size": "" + "general-tab": "Geral", + "metadata-tab": "Metadados", + "cover-tab": "Capa", + "info-tab": "Info", + "no-summary": "Sumário inexistente.", + "writers-title": "{{series-metadata-detail.writers-title}}", + "genres-title": "{{series-metadata-detail.genres-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "not-defined": "Não definido", + "read": "{{common.read}}", + "unread": "Não Lido", + "files": "Ficheiros", + "pages": "Páginas:", + "added": "Adicionado:", + "size": "Tamanho:" }, "card-detail-layout": { - "total-items": "" + "total-items": "{{count}} itens totais" }, "card-item": { - "cannot-read": "" + "cannot-read": "Não Pode Ler" }, "chapter-metadata-detail": { - "no-data": "", - "writers-title": "", - "publishers-title": "", - "characters-title": "", - "translators-title": "", - "letterers-title": "", - "colorists-title": "", - "inkers-title": "", - "pencillers-title": "", - "cover-artists-title": "", - "editors-title": "" + "no-data": "Metadados inexistentes", + "writers-title": "{{series-metadata-detail.writers-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "translators-title": "{{series-metadata-detail.translators-title}}", + "letterers-title": "{{series-metadata-detail.letterers-title}}", + "colorists-title": "{{series-metadata-detail.colorists-title}}", + "inkers-title": "{{series-metadata-detail.inkers-title}}", + "pencillers-title": "{{series-metadata-detail.pencillers-title}}", + "cover-artists-title": "{{series-metadata-detail.cover-artists-title}}", + "editors-title": "{{series-metadata-detail.editors-title}}" }, "cover-image-chooser": { "drag-n-drop": "", - "upload": "", - "upload-continued": "", - "url-label": "", - "load": "", - "back": "", - "reset-cover-tooltip": "", - "reset": "", - "image-num": "", - "apply": "", - "applied": "" + "upload": "Carregue", + "upload-continued": "uma imagem", + "url-label": "Url", + "load": "Carregar", + "back": "Voltar", + "reset-cover-tooltip": "Repor Imagem de Capa", + "reset": "{{common.reset}}", + "image-num": "Imagem {{num}}", + "apply": "{{common.apply}}", + "applied": "{{theme-manager.applied}}" }, "download-indicator": { - "progress": "" + "progress": "{{percentage}}% descarregado" }, "edit-series-relation": { - "description-part-1": "", - "description-part-2": "", + "description-part-1": "Sem certeza sobre que relação adicionar? Veja o nosso", + "description-part-2": "wiki para dicas.", "target-series": "", - "relationship": "", - "remove": "", - "add-relationship": "", - "parent": "" + "relationship": "Relação", + "remove": "Eliminar", + "add-relationship": "Adicionar Relação", + "parent": "{{relationship-pipe.parent}}" }, "entity-info-cards": { - "tags-title": "", - "characters-title": "", - "release-date-title": "", - "release-date-tooltip": "", - "age-rating-title": "", - "length-title": "", - "pages-count": "", - "words-count": "", - "reading-time-title": "", + "tags-title": "{{series-metadata-detail.tags-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "release-date-title": "Lançamento", + "release-date-tooltip": "Data de Lançamento", + "age-rating-title": "Classificação Etária", + "length-title": "Páginas Totais", + "pages-count": "{{num}} Páginas", + "words-count": "{{num}} Palavras", + "reading-time-title": "Tempo de Leitura", "date-added-title": "", - "size-title": "", - "id-title": "", - "links-title": "", - "isbn-title": "", - "last-read-title": "", - "less-than-hour": "", - "range-hours": "", - "hour": "", - "hours": "", - "read-time-title": "" + "size-title": "Tamanho", + "id-title": "ID", + "links-title": "{{series-metadata-detail.links-title}}", + "isbn-title": "ISBN", + "last-read-title": "Última Leitura", + "less-than-hour": "<1 Hora", + "range-hours": "{{value}} {{hourWord}}", + "hour": "Hora", + "hours": "Horas", + "read-time-title": "{{series-info-cards.read-time-title}}" }, "series-info-cards": { - "release-date-title": "", - "release-year-tooltip": "", - "age-rating-title": "", - "language-title": "", - "publication-status-title": "", - "publication-status-tooltip": "", - "scrobbling-title": "", - "scrobbling-tooltip": "", + "release-date-title": "{{entity-info-cards.release-date-title}}", + "release-year-tooltip": "Ano de Lançamento", + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "language-title": "Idioma", + "publication-status-title": "Publicação", + "publication-status-tooltip": "Estado de Publicação", + "scrobbling-title": "Scrobbling", + "scrobbling-tooltip": "Estado de Scrobbling", "on": "", "off": "", - "disabled": "", - "format-title": "", - "last-read-title": "", - "length-title": "", - "read-time-title": "", - "less-than-hour": "", - "hour": "", - "hours": "", - "time-left-title": "", - "ongoing": "", - "pages-count": "", - "words-count": "" + "disabled": "Desabilitado", + "format-title": "Formato", + "last-read-title": "Última Leitura", + "length-title": "Páginas Totais", + "read-time-title": "Tempo de Leitura", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "time-left-title": "Tempo Remanescente", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}" }, "bulk-add-to-collection": { - "title": "", - "promoted": "", - "close": "", - "filter-label": "", - "clear": "", - "no-data": "", - "loading": "", - "collection-label": "", - "create": "" + "title": "Adicionar a Coleção", + "promoted": "{{common.promoted}}", + "close": "{{common.close}}", + "filter-label": "Filtrar", + "clear": "{{common.clear}}", + "no-data": "Não há coleções criadas", + "loading": "{{common.loading}}", + "collection-label": "Coleção", + "create": "{{common.create}}" }, "entity-title": { - "special": "", - "issue-num": "", - "chapter": "" + "special": "Especial", + "issue-num": "Número #", + "chapter": "Capítulo" }, "external-series-card": { - "open-external": "" + "open-external": "Abrir Externo" }, "list-item": { - "read": "" + "read": "{{common.read}}" }, "manage-alerts": { - "description-part-1": "", - "description-part-2": "", - "filter-label": "", - "clear-alerts": "", - "extension-header": "", - "file-header": "", - "comment-header": "", - "details-header": "" + "description-part-1": "Esta tabela contém problemas encontrados durante a análise e leitura dos seus ficheiros. Pode limpar esta a lista a qualquer momento e preenchê-la novamente ao usar Análise da Biblioteca. A lista dos erros mais comuns e o seu significado pode ser encontrado no ", + "description-part-2": "wiki.", + "filter-label": "Filtrar", + "clear-alerts": "Limpar Alertas", + "extension-header": "Extensão", + "file-header": "Ficheiro", + "comment-header": "Comentário", + "details-header": "Detallhes" }, "manage-email-settings": { - "title": "", - "description": "", - "send-to-warning": "", - "email-url-label": "", - "email-url-tooltip": "", - "reset": "", - "test": "", - "host-name-label": "", - "host-name-tooltip": "", - "host-name-validation": "", - "reset-to-default": "", - "save": "" + "title": "Serviços de Email (SMTP)", + "description": "O Kavita inclui um serviço de email que possibilita o convite de utilizadores, repor palavra passe, etc. Os emails enviados pelo nosso serviço são imediatamente apagados. Pode usar o seu próprio serviço ao configurar o serviço {{link}}. Indique o URL do serviço e use o botão Testar para confirmar que funciona. Pode repor estas configurações a qualquer altura. Não existe maneira de desabilitar os emails para autenticação, apesar de não ser necessário um endereço de email válido. Os links de confirmação serão sempre guardados nos logos e mostrados no UI. Os emails de registo/confirmação não serão enviados se não estiver a aceder ao Kavita a partir de um URL público ou se não tiver a funcionalidade Nome do Host configurado.", + "send-to-warning": "Para que a funcionalidade Enviar para Dispositivo funcione é necessário usar o seu próprio serviço de email.", + "email-url-label": "URL do Serviço de Email", + "email-url-tooltip": "Use o URL completo do serviço de email. Não inclua a barra final.", + "reset": "{{common.reset}}", + "test": "Teste", + "host-name-label": "Nome do Host", + "host-name-tooltip": "Nome de domínio (do Proxy Inverso). Quando definido, a geração de emails irá usar sempre este valor.", + "host-name-validation": "O nome do host tem de começar por http(s) e não pode acabar com /", + "reset-to-default": "{{common.reset-to-default}}", + "save": "{{common.save}}" }, "manage-library": { - "title": "", - "add-library": "", - "no-data": "", - "loading": "", - "last-scanned-title": "", - "shared-folders-title": "", - "type-title": "", - "scan-library": "", - "delete-library": "", - "delete-library-by-name": "", - "edit-library": "", - "edit-library-by-name": "" + "title": "Bibliotecas", + "add-library": "Adicionar Biblioteca", + "no-data": "Não existem bibliotecas. Tente criar uma.", + "loading": "{{common.loading}}", + "last-scanned-title": "Último Scan:", + "shared-folders-title": "Pastas Partilhadas:", + "type-title": "Tipo:", + "scan-library": "Analisar Biblioteca", + "delete-library": "Eliminar Biblioteca", + "delete-library-by-name": "Eliminar {{name}}", + "edit-library": "Editar", + "edit-library-by-name": "Eliminar {{name}}" }, "manage-media-settings": { - "encode-as-description-part-1": "", - "encode-as-description-part-2": "", - "encode-as-description-part-3": "", - "encode-as-warning": "", - "media-warning": "", - "encode-as-label": "", - "encode-as-tooltip": "", - "bookmark-dir-label": "", - "bookmark-dir-tooltip": "", - "change": "", - "reset-to-default": "", - "reset": "", - "save": "", - "media-issue-title": "", - "scrobble-issue-title": "" + "encode-as-description-part-1": "Os formatos WebP/AVIF podem reduzir drasticamente os requisitos de espaço para os ficheiros. Estes formatos não são suportados por todos os browsers. Para saber se estas definições são apropriadas para a sua configuração, visite ", + "encode-as-description-part-2": "Posso usar WebP?", + "encode-as-description-part-3": "Posso usar AViF?", + "encode-as-warning": "Não é possível converter de volta para PNG depois da conversão para WebP/AVIF. Seria necessário refrescar as capas nas suas bibliotecas para regenerar as capas. Os marcadores e favicons não podem ser convertidos.", + "media-warning": "Tem de despoletar a tarefa de conversão de ficheiros na Aba Tarefas,", + "encode-as-label": "Guardar Media Como", + "encode-as-tooltip": "Todos os ficheiros geridos pelo Kavita (capas, marcadores, favicons) serão codificados para este tipo.", + "bookmark-dir-label": "Diretoria de Marcadores", + "bookmark-dir-tooltip": "Local onde os marcadores serão guardados. Os marcadores são ficheiros que podem ser grandes. Escolha uma localização com o espaço adequado. A diretoria é gerida; os outros ficheiros nesta diretoria podem ser eliminados. Se usar Docker, monte um volume adicional e use-o.", + "change": "Mudar", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "media-issue-title": "Problemas com Ficheiros", + "scrobble-issue-title": "Problemas de Scrobble", + "cover-image-size-label": "Tamanho da Imagem de Capa", + "cover-image-size-tooltip": "Com que tamanho devem ser geradas as imagens de capa. Nota: Tudo o que for maior que o valor por defeito provocará maiores tempos de load das páginas." }, "manage-scrobble-errors": { - "description": "", - "filter-label": "", - "clear-errors": "", - "series-header": "", - "created-header": "", - "comment-header": "", - "edit-header": "", - "edit-item-alt": "" + "description": "Esta tabela contém problemas encontrados durante o scrobbling. Esta lista não é gerida. Pode-a limpar sempre que quiser e esperar pelo próximo scrobble para ver alterações. Se existir uma série desconhecida, o melhor é corrigir o nome da série, ou o nome localizado, ou adicionar um weblink para o fornecedor.", + "filter-label": "Filtro", + "clear-errors": "Limpar Erros", + "series-header": "Séries", + "created-header": "Criado", + "comment-header": "Comentário", + "edit-header": "Editar", + "edit-item-alt": "Editar {{seriesName}}" }, "default-date-pipe": { - "never": "" + "never": "Nunca" }, "manage-settings": { - "notice": "", - "restart-required": "", - "base-url-label": "", - "base-url-tooltip": "", - "ip-address-label": "", - "ip-address-tooltip": "", - "port-label": "", - "port-tooltip": "", - "backup-label": "", - "backup-tooltip": "", - "log-label": "", - "log-tooltip": "", - "logging-level-label": "", + "notice": "Aviso:", + "restart-required": "A alteração de Porta, Url Base, Tamanho de Cache ou IPs requere um reinício manual do Kavita para terem efeito.", + "base-url-label": "Url Base", + "base-url-tooltip": "Use isto se quiser alojar o Kavita num url base, p.e., oseudominio.com/kavita. Não suportado com Docker com utilizador \"não root\".", + "ip-address-label": "Endereços IP", + "ip-address-tooltip": "Lista de endereços IP separados por vírgulas em que o servidor está à escuta. Isto está corrigido se estiver a usar Docker. Requer uma reinicialização para ter efeito.", + "port-label": "Porta", + "port-tooltip": "Porta em que o servidor está à escuta. Isto é corrigido se estiver a usar Docker. Requer uma reinicialização para ter efeito.", + "backup-label": "Dias de Backups", + "backup-tooltip": "O número de backups a manter. Por defeito é 30, o mínimo é 1, o máximo é 30.", + "log-label": "Dias de Logs", + "log-tooltip": "O número de logs a manter. Por defeito é 30, o mínimo é 1, o máximo é 30.", + "logging-level-label": "Nível de Logging", "logging-level-tooltip": "", - "cache-size-label": "", - "cache-size-tooltip": "", + "cache-size-label": "Tamanho de Cache", + "cache-size-tooltip": "A quantidade de memória permitida para fazer cache de APIs pesadas. Por defeito 75MB.", "on-deck-last-progress-label": "", "on-deck-last-progress-tooltip": "", "on-deck-last-chapter-add-label": "", "on-deck-last-chapter-add-tooltip": "", - "allow-stats-label": "", - "allow-stats-tooltip-part-1": "", - "allow-stats-tooltip-part-2": "", - "send-data": "", - "opds-label": "", - "opds-tooltip": "", - "enable-opds": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "enable-folder-watching": "", - "reset-to-default": "", - "reset": "", - "save": "", - "cache-size-validation": "", - "field-required": "", - "max-logs-validation": "", - "min-logs-validation": "", - "min-days-validation": "", - "min-cache-validation": "", - "max-backup-validation": "", - "min-backup-validation": "", - "ip-address-validation": "", - "base-url-validation": "" + "allow-stats-label": "Permitir a obtenção de dados de uso anónimos", + "allow-stats-tooltip-part-1": "Enviar dados de utilização anónimos para os servidores do Kavita. Isto inclui informação sobre as funcionalidades usadas, número de ficheiros, versão de SO, versão de instalação do Kavita, CPU e memória. Iremos usar esta informação para dar prioridade a novas funcionalidades, correção de bugs, e melhorias de desempenho. Requer uma reinicialização para ter efeito. Veja o ", + "allow-stats-tooltip-part-2": "para ver que dados são recolhidos.", + "send-data": "Enviar Dados", + "opds-label": "OPDS", + "opds-tooltip": "O suporte para OPDS permite aos utilizadores usar OPDS para ler e descarregar conteúdo do servidor.", + "enable-opds": "Ligar OPDS", + "folder-watching-label": "Monitorizar Pastas", + "folder-watching-tooltip": "Permite ao Kavita monitorizar pastas das bibliotecas para detectar alterações e invocar a análise dessas alterações. Isto permite que o conteúdo seja atualizado sem ser necessário invocar análises manualmente ou esperar pelas análises noturnas.", + "enable-folder-watching": "Ligar Monitorização de Pastas", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "Tem de ter no mínimo 50 MB.", + "field-required": "{{validation.field-required}}", + "max-logs-validation": "Não pode ter mais que {{num}} logs", + "min-logs-validation": "Tem de ter no mínimo 1 log", + "min-days-validation": "Tem de ter no mínimo 1 dia", + "min-cache-validation": "Deve estar a 50 MB.", + "max-backup-validation": "Não pode ter mais que {{num}} backup", + "min-backup-validation": "Tem de ter no mínimo 1 backup", + "ip-address-validation": "Os endereços IP têm de ser endereços IPv4 ou IPv6 válidos", + "base-url-validation": "O URL Base tem de começar e terminar com /" }, "manage-system": { - "title": "", - "version-title": "", - "installId-title": "", - "more-info-title": "", - "home-page-title": "", - "wiki-title": "", - "discord-title": "", - "donations-title": "", - "source-title": "", - "feature-request-title": "" + "title": "Sobre o Sistema", + "version-title": "Versão", + "installId-title": "ID de Instalação", + "more-info-title": "Mais Info", + "home-page-title": "Página inicial:", + "wiki-title": "Wiki:", + "discord-title": "Discord:", + "donations-title": "Donativos:", + "source-title": "Código fonte:", + "feature-request-title": "Pedidos de Funcionalidades" }, "manage-tasks-settings": { - "title": "", - "library-scan-label": "", - "library-scan-tooltip": "", - "library-database-backup-label": "", - "library-database-backup-tooltip": "", - "adhoc-tasks-title": "", - "job-title-header": "", - "description-header": "", - "action-header": "", - "reset-to-default": "", - "reset": "", - "save": "", - "recurring-tasks-title": "", - "last-executed-header": "", - "cron-header": "", - "convert-media-task": "", - "convert-media-task-desc": "", - "convert-media-success": "", - "bust-cache-task": "", + "title": "Tarefas Recorrentes", + "library-scan-label": "Análisar Biblioteca", + "library-scan-tooltip": "A frequência com que o Kavita analisa e refresca os metadados dos ficheiros das bibliotecas.", + "library-database-backup-label": "Cópia de segurança da BD da Biblioteca", + "library-database-backup-tooltip": "Com que frequência o Kavita faz uma cópia de segurança da BD.", + "adhoc-tasks-title": "Tarefas Ad-hoc", + "job-title-header": "Título da Tarefa", + "description-header": "Descrição", + "action-header": "Ação", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}", + "last-executed-header": "Última Execução", + "cron-header": "Cron", + "convert-media-task": "Converter Media para Codificação de Destino", + "convert-media-task-desc": "Corre uma tarefa de longa duração que irá converter para a codificação de destino todos os media geridos pelo kavita. Este processo é lento (em especial em dispositivos ARM).", + "convert-media-success": "Conversão de Media para Codificação de Destino está agendada", + "bust-cache-task": "Eliminar Cache", "bust-cache-task-desc": "", - "bust-cache-task-success": "", - "clear-reading-cache-task": "", - "clear-reading-cache-task-desc": "", - "clear-reading-cache-task-success": "", - "clean-up-want-to-read-task": "", - "clean-up-want-to-read-task-desc": "", - "clean-up-want-to-read-task-success": "", - "backup-database-task": "", - "backup-database-task-desc": "", - "backup-database-task-success": "", - "download-logs-task": "", - "download-logs-task-desc": "", - "analyze-files-task": "", + "bust-cache-task-success": "Cache de Kavita+ eliminada", + "clear-reading-cache-task": "Limpar Cache de Leitura", + "clear-reading-cache-task-desc": "Limpa a cache de ficheiros para leitura. É útil quando atualiza um ficheiro que tenha sido lido nas últimas 24 horas.", + "clear-reading-cache-task-success": "A cache foi limpa", + "clean-up-want-to-read-task": "Limpar Leituras Futuras", + "clean-up-want-to-read-task-desc": "Remove das Leituras Futuras todas as séries que estejam completamente lidas e que tenham um estado de publicação Concluído. Corre a cada 24 horas.", + "clean-up-want-to-read-task-success": "As Leituras Futuras foram limpas", + "backup-database-task": "Criar Cópia de Segurança da BD", + "backup-database-task-desc": "Cria uma cópia de segurança da BD, marcadores, temas, capas carregadas manualmente, e ficheiros de configuração.", + "backup-database-task-success": "Foi agendada a tarefa para criar uma cópia de segurança da BD", + "download-logs-task": "Descarregar Logs", + "download-logs-task-desc": "Junta todos os ficheiros de log num ficheiro zip e descarrega-o.", + "analyze-files-task": "Analisar Ficheiros", "analyze-files-task-desc": "", - "analyze-files-task-success": "", - "check-for-updates-task": "", - "check-for-updates-task-desc": "" + "analyze-files-task-success": "Foi agendada a análise de ficheiros", + "check-for-updates-task": "Verificar se há Updates", + "check-for-updates-task-desc": "Verificar se há algum lançamento Estável posterior à sua versão." }, "manage-users": { - "title": "", - "invite": "", + "title": "Utilizadores Ativos", + "invite": "Convidar", "you-alt": "", - "pending-title": "", - "delete-user-tooltip": "", - "delete-user-alt": "", - "edit-user-tooltip": "", - "edit-user-alt": "", - "resend-invite-tooltip": "", - "resend-invite-alt": "", - "setup-user-tooltip": "", - "setup-user-alt": "", - "change-password-tooltip": "", - "change-password-alt": "", - "resend": "", - "setup": "", - "last-active-title": "", - "roles-title": "", - "none": "", - "never": "", + "pending-title": "Pendente", + "delete-user-tooltip": "Eliminar Utilizador", + "delete-user-alt": "Eliminar Utilizador {{user}}", + "edit-user-tooltip": "Editar", + "edit-user-alt": "Editar Utilizador {{user}}", + "resend-invite-tooltip": "Reenviar Convite", + "resend-invite-alt": "Reenviar Convite {{user}}", + "setup-user-tooltip": "Configurar Utilizador", + "setup-user-alt": "Configurar Utilizador {{user}}", + "change-password-tooltip": "Alterar Palavra Passe", + "change-password-alt": "Alterar Palavra Passe {{user}}", + "resend": "Reenviar", + "setup": "Configurar", + "last-active-title": "Última Atividade:", + "roles-title": "Funções:", + "none": "Nenhuma", + "never": "Nunca", "online-now-tooltip": "", - "sharing-title": "", - "no-data": "", - "loading": "" + "sharing-title": "A Partilhar:", + "no-data": "Não existem outros utilizadores.", + "loading": "{{common.loading}}" }, "edit-collection-tags": { - "title": "", - "required-field": "", - "save": "", - "close": "", - "cancel": "", - "general-tab": "", - "cover-image-tab": "", - "series-tab": "", - "name-label": "", - "name-validation": "", - "promote-label": "", - "promote-tooltip": "", - "summary-label": "", - "series-title": "", - "deselect-all": "", - "select-all": "" + "title": "Editar Coleção {{collectionName}}", + "required-field": "{{validation.required-field}}", + "save": "{{common.save}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "general-tab": "Geral", + "cover-image-tab": "Imagem de Capa", + "series-tab": "Séries", + "name-label": "Nome", + "name-validation": "O nome tem de ser único", + "promote-label": "Promover", + "promote-tooltip": "A promoção significa que a etiqueta será vista ao nível do servidor, e não apenas pelos administradores. Todas as séries que tenham esta etiqueta continuarão a ter restrições de acesso dos utilizadores.", + "summary-label": "Sumário", + "series-title": "Aplicável a Séries", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}" }, "library-detail": { - "library-tab": "", - "recommended-tab": "" + "library-tab": "Biblioteca", + "recommended-tab": "Recomendado" }, "library-recommended": { - "no-data": "", + "no-data": "Nada a mostrar. Adicione metadados à sua biblioteca, leia ou classifique algo. Esta biblioteca também pode ter as recomendações desligadas.", "more-in-genre": "", - "rediscover": "", - "highly-rated": "", + "rediscover": "Redescobrir", + "highly-rated": "Bem Classificado", "quick-catchups": "", - "quick-reads": "", - "on-deck": "" + "quick-reads": "Leituras Rápidas", + "on-deck": "{{dashboard.on-deck-title}}" }, "admin-dashboard": { - "title": "", - "general-tab": "", - "users-tab": "", - "libraries-tab": "", - "media-tab": "", - "logs-tab": "", - "email-tab": "", - "tasks-tab": "", - "statistics-tab": "", - "system-tab": "", - "kavita+-tab": "", - "kavita+-desc-part-1": "", - "kavita+-desc-part-2": "", - "kavita+-desc-part-3": "" + "title": "Painel do Administrador", + "general-tab": "Geral", + "users-tab": "Utilizadores", + "libraries-tab": "Bibliotecas", + "media-tab": "Media", + "logs-tab": "Logs", + "email-tab": "Email", + "tasks-tab": "Tarefas", + "statistics-tab": "Estatísticas", + "system-tab": "Sistema", + "kavita+-tab": "Kavita+", + "kavita+-desc-part-1": "O Kavita+ é um serviço de subscrição premium que desbloqueia funcionalidades para todos os utilizadores desta instância Kavita. Compre uma subscrição para desbloquear ", + "kavita+-desc-part-2": "os benefícios premium", + "kavita+-desc-part-3": "agora!" }, "collection-detail": { - "no-data": "", - "no-data-filtered": "", - "title-alt": "" + "no-data": "Não existem itens. Tente adicionar uma série.", + "no-data-filtered": "Não existem itens para o filtro atual.", + "title-alt": "Kavita - Coleção {{collectionName}}" }, "all-collections": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "Coleções", + "item-count": "{{common.item-count}}", + "no-data": "Não existem coleções.", + "create-one-part-1": "Tente criar", + "create-one-part-2": "uma" }, "carousel-reel": { - "prev-items": "", - "next-items": "" + "prev-items": "Itens Anteriores", + "next-items": "Itens Seguintes" }, "draggable-ordered-list": { - "instructions-alt": "", - "reorder-label": "", - "remove-item-alt": "" + "instructions-alt": "Quando coloca um número no campo para reordenar, o item será inserido nesse local e todos os outros itens terão a sua ordem atualizada.", + "reorder-label": "Reordenar", + "remove-item-alt": "Remover item" }, "reading-lists": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "Listas de Leitura", + "item-count": "{{common.item-count}}", + "no-data": "Não existem listas de leitura.", + "create-one-part-1": "Tente criar", + "create-one-part-2": "uma" }, "reading-list-item": { - "remove": "", - "read": "" + "remove": "{{common.remove}}", + "read": "{{common.read}}" }, "reading-list-detail": { - "item-count": "", - "page-settings-title": "", - "remove-read": "", + "item-count": "{{common.item-count}}", + "page-settings-title": "Definições de Página", + "remove-read": "Remover Leitura", "order-numbers-label": "", - "continue": "", - "read": "", - "read-options-alt": "", - "incognito-alt": "", - "no-data": "" + "continue": "Continuar", + "read": "{{common.read}}", + "read-options-alt": "Opções de leitura", + "incognito-alt": "(Incógnito)", + "no-data": "Nada adicionado", + "characters-title": "{{series-metadata-detail.characters-title}}" }, "events-widget": { - "title-alt": "", + "title-alt": "Atividade", "dismiss-all": "", - "update-available": "", - "downloading-item": "", - "more-info": "", - "close": "", - "users-online-count": "", - "active-events-title": "", + "update-available": "Atualização disponível", + "downloading-item": "A descarregar {{item}}", + "more-info": "Clique para ver mais informação", + "close": "{{common.close}}", + "users-online-count": "{{num}} Utilizadores ligados", + "active-events-title": "Eventos Ativos:", "no-data": "" }, "shortcuts-modal": { - "title": "", - "close": "", - "prev-page": "", - "next-page": "", - "go-to": "", - "bookmark": "", - "double-click": "", - "close-reader": "", - "toggle-menu": "" + "title": "Atalhos de Teclado", + "close": "{{common.close}}", + "prev-page": "Ir para a página anterior", + "next-page": "Ir para a página seguinte", + "go-to": "Abrir diálogo Ir para Página", + "bookmark": "Adicionar marcador a página atual", + "double-click": "duplo clique", + "close-reader": "Fechar leitor", + "toggle-menu": "Abrir/Fechar Menu" }, "grouped-typeahead": { - "files": "", - "chapters": "", - "people": "", - "tags": "", - "genres": "", - "libraries": "", - "reading-lists": "", - "collections": "", - "close": "", - "loading": "" + "files": "Ficheiros", + "chapters": "Capítulos", + "people": "Pessoas", + "tags": "Etiquetas", + "genres": "Géneros", + "libraries": "Bibliotecas", + "reading-lists": "Listas de Leitura", + "collections": "Coleções", + "close": "{{common.close}}", + "loading": "{{common.loading}}" }, "nav-header": { - "skip-alt": "", - "search-series-alt": "", - "search-alt": "", - "promoted": "", - "no-data": "", - "scroll-to-top-alt": "", - "server-settings": "", - "settings": "", - "help": "", - "announcements": "", - "logout": "" + "skip-alt": "Saltar para o conteúdo principal", + "search-series-alt": "Procurar séries", + "search-alt": "Pesquisar…", + "promoted": "(promovido)", + "no-data": "Sem resultados", + "scroll-to-top-alt": "Ir para o Topo", + "server-settings": "Definições do Servidor", + "settings": "Definições", + "help": "Ajuda", + "announcements": "Anúncios", + "logout": "Terminar Sessão" }, "add-to-list-modal": { - "title": "", - "close": "", - "filter-label": "", - "promoted-alt": "", - "no-data": "", - "loading": "", - "reading-list-label": "", - "create": "" + "title": "Adicionar a Lista de Leitura", + "close": "{{common.close}}", + "filter-label": "Filtro", + "promoted-alt": "Promovido", + "no-data": "Ainda não existem listas criadas", + "loading": "{{common.loading}}", + "reading-list-label": "Lista de Leitura", + "create": "{{common.create}}" }, "edit-reading-list-modal": { - "title": "", - "general-tab": "", - "cover-image-tab": "", - "close": "", - "save": "", - "year-validation": "", - "month-validation": "", - "name-unique-validation": "", - "required-field": "", - "summary-label": "", - "year-label": "", - "month-label": "", + "title": "Editar Lista de Leitura: {{nome}}", + "general-tab": "Geral", + "cover-image-tab": "Imagem de Capa", + "close": "{{common.close}}", + "save": "{{common.save}}", + "year-validation": "Tem de ser maior que 1000, 0 ou em branco", + "month-validation": "Tem de estar entre 1 e 12 ou em branco", + "name-unique-validation": "O nome tem de ser único", + "required-field": "{{validation.required-field}}", + "summary-label": "Sumário", + "year-label": "Ano", + "month-label": "Mês", "ending-title": "", "starting-title": "", - "promote-label": "", - "promote-tooltip": "" + "promote-label": "Promover", + "promote-tooltip": "A promoção significa que a etiqueta pode ser vista ao nível do servidor, e não apenas por administradores. As séries que tenham esta etiqueta continuarão a ter restrições de acesso ao nível de utilizador." }, "import-cbl-modal": { - "close": "", - "title": "", - "import-description": "", - "validate-description": "", - "validate-warning": "", + "close": "{{common.close}}", + "title": "Importar CBL", + "import-description": "Importe um ficheiro .cbl para começar. O Kavita irá fazer várias verificações antes de importar. Alguns passos poderão impedir que se avance por haver problemas com o ficheiro.", + "validate-description": "Todos os ficheiros foram validados para verificar se há operações pendentes na lista. As listas que tiverem falhado não passarão para o passo seguinte. Corrija o CBL e tente novamente.", + "validate-warning": "Existem problemas com o CBL que impedem a importação. Corrija estes problemas e tente novamente.", "validate-no-issue": "", - "validate-no-issue-description": "", + "validate-no-issue-description": "Não existem problemas com o CBL, pressione seguinte.", "dry-run-description": "", - "prev": "", - "import": "", - "restart": "", - "next": "", - "import-step": "", - "validate-cbl-step": "", + "prev": "Ant", + "import": "Importar", + "restart": "Reiniciar", + "next": "Seguinte", + "import-step": "Importar CBLs", + "validate-cbl-step": "Validar CBL", "dry-run-step": "", - "final-import-step": "" + "final-import-step": "Passo Final" }, "pdf-reader": { - "loading-message": "", - "incognito-mode": "", - "light-theme-alt": "", - "dark-theme-alt": "", - "close-reader-alt": "" - }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" + "loading-message": "A carregar....PDFs podem demorar mais que o esperado", + "incognito-mode": "Modo Incógnito", + "light-theme-alt": "Tema Claro", + "dark-theme-alt": "Tema Escuro", + "close-reader-alt": "Fechar Leitor" }, "manga-reader": { - "back": "", - "save-globally": "", + "back": "Voltar", + "save-globally": "Guardar Globalmente", "incognito-alt": "", - "incognito-title": "", - "shortcuts-menu-alt": "", - "prev-page-tooltip": "", - "next-page-tooltip": "", - "prev-chapter-tooltip": "", - "next-chapter-tooltip": "", - "first-page-tooltip": "", - "last-page-tooltip": "", - "left-to-right-alt": "", - "right-to-left-alt": "", - "reading-direction-tooltip": "", - "reading-mode-tooltip": "", - "collapse": "", - "fullscreen": "", - "settings-tooltip": "", - "image-splitting-label": "", + "incognito-title": "Modo Incógnito:", + "shortcuts-menu-alt": "Modal de Atalhos de Teclado", + "prev-page-tooltip": "Página Anterior", + "next-page-tooltip": "Página Seguinte", + "prev-chapter-tooltip": "Capítulo/Volume Ant", + "next-chapter-tooltip": "Capítulo/Volume Seguinte", + "first-page-tooltip": "Primeira Página", + "last-page-tooltip": "Última Página", + "left-to-right-alt": "Esquerda para Direita", + "right-to-left-alt": "Direita para Esquerda", + "reading-direction-tooltip": "Direção de Leitura: ", + "reading-mode-tooltip": "Modo de Leitura", + "collapse": "Colapsar", + "fullscreen": "Ecrã Completo", + "settings-tooltip": "Definições", + "image-splitting-label": "Separação de Imagens", "image-scaling-label": "", - "height": "", - "width": "", - "original": "", - "auto-close-menu-label": "", + "height": "Altura", + "width": "Largura", + "original": "Original", + "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", "swipe-enabled-label": "", - "enable-comic-book-label": "", - "brightness-label": "", - "first-time-reading-manga": "", - "layout-mode-switched": "", - "no-next-chapter": "", - "no-prev-chapter": "", - "user-preferences-updated": "" + "enable-comic-book-label": "Emular livro de BD", + "brightness-label": "Brilho", + "first-time-reading-manga": "Toque na imagem a qualquer momento para abrir o menu. Pode alterar definições ou ir para uma página ao clicar na barra de progresso. Toque nos lados da imagem para ir para a página seguinte/anterior.", + "layout-mode-switched": "O modo de layout foi mudado para Individual por não haver espaço suficiente para mostrar o layout duplo", + "no-next-chapter": "Não há Capítulo Seguinte", + "no-prev-chapter": "Não há Capítulo Anterior", + "user-preferences-updated": "Preferência de utilizador atualizadas", + "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}" }, "metadata-filter": { - "filter-title": "", - "format-label": "", - "format-tooltip": "", - "libraries-label": "", - "collections-label": "", - "genres-label": "", - "tags-label": "", - "cover-artist-label": "", - "writer-label": "", - "publisher-label": "", - "penciller-label": "", - "letterer-label": "", - "inker-label": "", - "editor-label": "", - "colorist-label": "", - "character-label": "", - "translator-label": "", - "read-progress-label": "", - "unread": "", - "read": "", + "filter-title": "Filtro", + "format-label": "Formato", + "libraries-label": "Bibliotecas", + "collections-label": "Coleções", + "genres-label": "Géneros", + "tags-label": "Etiquetas", + "cover-artist-label": "Artista de Capa", + "writer-label": "Escritor", + "publisher-label": "Editora", + "penciller-label": "Desenhista", + "letterer-label": "Letrista", + "inker-label": "Arte-Finalista", + "editor-label": "Editor", + "colorist-label": "Colorista", + "character-label": "Personagem", + "translator-label": "Tradutor", + "read-progress-label": "Progresso da Leitura", + "unread": "Não Lido", + "read": "Lido", "in-progress": "", - "rating-label": "", - "age-rating-label": "", - "language-label": "", - "publication-status-label": "", - "series-name-label": "", - "series-name-tooltip": "", - "release-label": "", - "min": "", - "max": "", - "sort-by-label": "", - "ascending-alt": "", - "descending-alt": "", - "reset": "", - "apply": "" + "rating-label": "Classificação", + "age-rating-label": "Classificação Etária", + "language-label": "Idioma", + "publication-status-label": "Estado de Publicação", + "series-name-label": "Nome da Série", + "series-name-tooltip": "O nome da série será filtrado por Nome, Nome de Ordenção, ou Nome Localizado", + "release-label": "Lançamento", + "min": "Min", + "max": "Max", + "sort-by-label": "Ordenar Por", + "ascending-alt": "Ascendente", + "descending-alt": "Descendente", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "limit-label": "Limitar A" }, "sort-field-pipe": { - "sort-name": "", - "created": "", - "last-modified": "", - "last-chapter-added": "", - "time-to-read": "", - "release-year": "" + "sort-name": "Nome para Ordenação", + "created": "Criado", + "last-modified": "Última Alteração", + "last-chapter-added": "Item Adicionado", + "time-to-read": "Tempo de Leitura", + "release-year": "Ano de Lançamento" }, "edit-series-modal": { - "title": "", - "general-tab": "", - "metadata-tab": "", - "people-tab": "", - "web-links-tab": "", - "cover-image-tab": "", - "related-tab": "", - "info-tab": "", - "collections-label": "", - "genres-label": "", - "tags-label": "", - "cover-artist-label": "", - "writer-label": "", - "publisher-label": "", - "penciller-label": "", - "letterer-label": "", - "inker-label": "", - "editor-label": "", - "colorist-label": "", - "character-label": "", - "translator-label": "", - "language-label": "", - "age-rating-label": "", - "publication-status-label": "", - "required-field": "", - "close": "", - "name-label": "", - "sort-name-label": "", + "title": "Detalhes de {{seriesName}}", + "general-tab": "Geral", + "metadata-tab": "Metadados", + "people-tab": "Pessoas", + "web-links-tab": "Links Web", + "cover-image-tab": "Imagem de Capa", + "related-tab": "Relacionado", + "info-tab": "Info", + "collections-label": "Coleções", + "genres-label": "Géneros", + "tags-label": "Etiquetas", + "cover-artist-label": "Artista de Capa", + "writer-label": "Escritor", + "publisher-label": "Editora", + "penciller-label": "Desenhista", + "letterer-label": "Letrista", + "inker-label": "Arte-Finalista", + "editor-label": "Editor", + "colorist-label": "Colorista", + "character-label": "Personagem", + "translator-label": "Tradutor", + "language-label": "Idioma", + "age-rating-label": "Classificação Etária", + "publication-status-label": "Estado de Publicação", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "name-label": "Nome", + "sort-name-label": "Nome para Ordenação", "localized-name-label": "", - "summary-label": "", - "release-year-label": "", - "web-link-description": "", - "web-link-label": "", - "add-link-alt": "", - "remove-link-alt": "", - "cover-image-description": "", - "save": "", - "field-locked-alt": "", - "info-title": "", - "library-title": "", - "format-title": "", - "created-title": "", - "last-read-title": "", - "last-added-title": "", - "last-scanned-title": "", - "folder-path-title": "", - "publication-status-title": "", - "total-pages-title": "", - "total-items-title": "", - "max-items-title": "", - "size-title": "", - "loading": "", - "added-title": "", - "last-modified-title": "", - "view-files": "", - "pages-title": "", - "chapter-title": "", - "volume-num": "", + "summary-label": "Sumário", + "release-year-label": "Ano de Lançamento", + "web-link-description": "Aqui pode adicionar diferentes links para serviços externos.", + "web-link-label": "Link Web", + "add-link-alt": "Adicionar Link", + "remove-link-alt": "Remover Link", + "cover-image-description": "Carregue uma nova imagem de capa à sua escolha. Pressione em Guardar para carregar e substituir a capa.", + "save": "{{common.save}}", + "field-locked-alt": "Campo bloqueado", + "info-title": "Informação", + "library-title": "Biblioteca:", + "format-title": "Formato:", + "created-title": "Criado:", + "last-read-title": "Última Leitura:", + "last-added-title": "Último Item Adicionado:", + "last-scanned-title": "Última Análise:", + "folder-path-title": "Caminho da Pasta:", + "publication-status-title": "Estado de Publicação:", + "total-pages-title": "Páginas Totais:", + "total-items-title": "Itens Totais:", + "max-items-title": "Itens Máximos:", + "size-title": "Tamanho:", + "loading": "{{common.loading}}", + "added-title": "Adicionado:", + "last-modified-title": "Última Alteração:", + "view-files": "Ver Ficheiros", + "pages-title": "Páginas:", + "chapter-title": "Capítulo:", + "volume-num": "{{common.volume-num}}", "highest-count-tooltip": "", "max-issue-tooltip": "" }, "day-breakdown": { "title": "", - "x-axis-label": "", - "y-axis-label": "" + "x-axis-label": "Dia da Semana", + "y-axis-label": "Eventos de Leitura" }, "file-breakdown-stats": { - "format-title": "", - "format-tooltip": "", - "visualisation-label": "", - "data-table-label": "", - "extension-header": "", - "format-header": "", - "total-size-header": "", - "total-files-header": "", - "not-classified": "", + "format-title": "Formato", + "format-tooltip": "Não Classificado significa que o Kavita não analisou alguns ficheiros. Isto acontece em ficheiros que existiam antes da v0.7. Poderá ser necessário correr uma análise forçada a partir das definições da Biblioteca.", + "visualisation-label": "Visualização", + "data-table-label": "Tabela de Dados", + "extension-header": "Extensão", + "format-header": "Formato", + "total-size-header": "Tamanho Total", + "total-files-header": "Ficheiros Totais", + "not-classified": "Não Classificado", "total-file-size-title": "" }, "reading-activity": { "title": "", - "legend-label": "", - "x-axis-label": "", - "y-axis-label": "", + "legend-label": "Formatos", + "x-axis-label": "Tempo", + "y-axis-label": "Horas de Leitura", "no-data": "", - "time-frame-label": "" + "time-frame-label": "", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" }, "manga-format-stats": { - "title": "", - "visualisation-label": "", - "data-table-label": "", - "format-header": "", + "title": "Formato", + "visualisation-label": "Visualização", + "data-table-label": "Tabela de Dados", + "format-header": "Formato", "count-header": "" }, "publication-status-stats": { - "title": "", - "visualisation-label": "", - "data-table-label": "", - "year-header": "", + "title": "Estado de Publicação", + "visualisation-label": "Visualização", + "data-table-label": "Tabela de Dados", + "year-header": "Ano", "count-header": "" }, "server-stats": { - "total-series-label": "", - "total-series-tooltip": "", - "total-volumes-label": "", - "total-volumes-tooltip": "", - "total-files-label": "", - "total-files-tooltip": "", - "total-size-label": "", - "total-genres-label": "", - "total-genres-tooltip": "", - "total-tags-label": "", - "total-tags-tooltip": "", + "total-series-label": "Total de Séries", + "total-series-tooltip": "Total de Séries: {{count}}", + "total-volumes-label": "Volumes Totais", + "total-volumes-tooltip": "Volumes Totais: {{count}}", + "total-files-label": "Ficheiros Totais", + "total-files-tooltip": "Ficheiros Totais: {{count}}", + "total-size-label": "Tamanho Total", + "total-genres-label": "Géneros Totais", + "total-genres-tooltip": "Géneros Totais: {{count}}", + "total-tags-label": "Etiquetas Totais", + "total-tags-tooltip": "Etiquetas Totais: {{count}}", "total-people-label": "", "total-people-tooltip": "", - "total-read-time-label": "", - "total-read-time-tooltip": "", - "series": "", - "reads": "", - "release-years-title": "", - "most-active-users-title": "", - "popular-libraries-title": "", - "popular-series-title": "", - "recently-read-title": "", - "genre-count": "", - "tag-count": "", - "people-count": "", - "tags": "", - "people": "", - "genres": "" + "total-read-time-label": "Tempo Total de Leitura", + "total-read-time-tooltip": "Tempo Total de Leitura: {{count}}", + "series": "séries", + "reads": "leituras", + "release-years-title": "Anos de Lançamento", + "most-active-users-title": "Utilizadores Mais Ativos", + "popular-libraries-title": "Bibliotecas Populares", + "popular-series-title": "Séries Populares", + "recently-read-title": "Lido Recentemente", + "genre-count": "{{num}} Géneros", + "tag-count": "{{num}} Etiquetas", + "people-count": "{{num}} Pessoas", + "tags": "Etiquetas", + "people": "Pessoas", + "genres": "Géneros" }, "errors": { - "series-doesnt-exist": "", - "collection-invalid-access": "", - "unknown-crit": "", - "user-not-auth": "", - "error-code": "", - "download": "", - "not-found": "", - "generic": "", + "series-doesnt-exist": "Esta série já não existe", + "collection-invalid-access": "Não tem acesso a qualquer biblioteca a que esta etiqueta pertence, ou esta coleção é inválida", + "unknown-crit": "Ocorreu um erro crítico desconhecido", + "user-not-auth": "O utilizador não está autenticado", + "error-code": "Erro {{num}}", + "download": "Ocorreu um problema a descarregar este ficheiro ou não tem as permissões necessárias", + "not-found": "Esse url não existe", + "generic": "Aconteceu algo de errado", "rejected-cover-upload": "", - "invalid-confirmation-url": "", - "invalid-confirmation-email": "", - "invalid-password-reset-url": "" + "invalid-confirmation-url": "Url de confirmação inválido", + "invalid-confirmation-email": "Email de confirmação inválido", + "invalid-password-reset-url": "Url para repor palavra passe inválido" }, "toasts": { - "regen-cover": "", - "no-pages": "", + "regen-cover": "Foi agendada uma tarefa para gerar novamente a imagem de capa", + "no-pages": "Não existem páginas. O Kavita não conseguiu ler este arquivo.", "download-in-progress": "", - "scan-queued": "", - "server-settings-updated": "", - "reset-ip-address": "", - "reset-base-url": "", - "unauthorized-1": "", - "unauthorized-2": "", - "no-updates": "", - "confirm-delete-user": "", - "user-deleted": "", - "email-sent-to-user": "", - "click-email-link": "", - "series-added-to-collection": "", - "no-series-collection-warning": "", - "collection-updated": "", - "reading-list-deleted": "", - "reading-list-updated": "", - "confirm-delete-reading-list": "", - "item-removed": "", - "nothing-to-remove": "", - "series-added-to-reading-list": "", - "volumes-added-to-reading-list": "", - "chapter-added-to-reading-list": "", - "multiple-added-to-reading-list": "", - "select-files-warning": "", - "reading-list-imported": "", + "scan-queued": "Análise agendada para {{name}}", + "server-settings-updated": "Definições do servidor atualizadas", + "reset-ip-address": "Endereços IP Repostos", + "reset-base-url": "Url Base Reposto", + "unauthorized-1": "Não tem autorização para ver esta página.", + "unauthorized-2": "Não Autorizado", + "no-updates": "Nenhuma atualização disponível", + "confirm-delete-user": "Tem a certeza que quer eliminar este utilizador?", + "user-deleted": "{{user}} foi eliminado", + "email-sent-to-user": "Email enviado para {{user}}", + "click-email-link": "Por favor clique neste link para confirmar o seu email. Tem de confirmar para poder fazer login.", + "series-added-to-collection": "Série adicionada à coleção {{collectionName}}", + "no-series-collection-warning": "Cuidado! Nenhuma série foi selecionada, por isso ao guardar irá eliminar a Coleção. Tem a certeza que deseja continuar?", + "collection-updated": "Coleção atualizada", + "reading-list-deleted": "Lista de Leitura eliminada", + "reading-list-updated": "Lista de leitura atualizada", + "confirm-delete-reading-list": "Tem a certeza que quer eliminar a lista de leitura? Este passo não pode ser revertido.", + "item-removed": "Item eliminado", + "nothing-to-remove": "Nada para eliminar", + "series-added-to-reading-list": "Série adicionada a lista de leitura", + "volumes-added-to-reading-list": "Volume adicionado a lista de leitura", + "chapter-added-to-reading-list": "Capítulo adicionado a lista de leitura", + "multiple-added-to-reading-list": "Capítulos e Volumes adicionados a lista de leitura", + "select-files-warning": "Tem de selecionar ficheiros para avançar", + "reading-list-imported": "Lista de Leitura importada", "incognito-off": "", - "email-service-reset": "", - "email-service-reachable": "", - "email-service-unresponsive": "", + "email-service-reset": "Repor Serviço de Email", + "email-service-reachable": "O Serviço de Email está acessível", + "email-service-unresponsive": "O Url do Serviço de Email não respondeu.", "refresh-covers-queued": "", - "library-file-analysis-queued": "", - "entity-read": "", - "entity-unread": "", - "mark-read": "", - "mark-unread": "", - "series-removed-want-to-read": "", - "series-deleted": "", - "file-send-to": "", - "theme-missing": "", - "email-sent": "", + "library-file-analysis-queued": "Agendada análise de ficheiros da Biblioteca para {{name}}", + "entity-read": "{{name}} lido", + "entity-unread": "{{name}} não lido", + "mark-read": "Marcado como Lido", + "mark-unread": "Marcado como Não Lido", + "series-removed-want-to-read": "Série removida da lista Leituras Futuras", + "series-deleted": "Série eliminada", + "file-send-to": "Ficheiro(s) enviado(s) por email para {{name}}", + "theme-missing": "O tema ativo já não existe. Por favor refresque a página.", + "email-sent": "Email enviado para {{email}}", "k+-license-saved": "", - "k+-unlocked": "", - "k+-error": "", + "k+-unlocked": "Kavita+ desbloqueado!", + "k+-error": "Ocorreu um erro ao ativar a licença. Por favor tente novamente.", "k+-delete-key": "", - "library-deleted": "", - "copied-to-clipboard": "", + "library-deleted": "A biblioteca {{name}} foi removida", + "copied-to-clipboard": "Copiado para a área de transferência", "book-settings-info": "", "no-next-chapter": "", "no-prev-chapter": "", "load-next-chapter": "", "load-prev-chapter": "", - "account-registration-complete": "", - "account-migration-complete": "", - "password-reset": "", - "password-updated": "", + "account-registration-complete": "Registo concluído", + "account-migration-complete": "Migração de conta concluída", + "password-reset": "Repor palavra passe", + "password-updated": "A palavra passe foi atualizada", "forced-scan-queued": "", - "library-created": "", - "anilist-token-updated": "", - "age-restriction-updated": "", - "email-sent-to-no-existing": "", - "email-sent-to": "", + "library-created": "Biblioteca criada com sucesso. Foi iniciada uma análise.", + "anilist-token-updated": "Foi atualizado o Token AniList", + "age-restriction-updated": "A Restrição Etária foi atualizada", + "email-sent-to-no-existing": "Foi enviado um email para {{email}} para confirmação.", + "email-sent-to": "Foi enviado um email para o seu antigo endereço de email para confirmação.", "change-email-private": "", - "device-updated": "", - "device-created": "", + "device-updated": "Dispositivo atualizado", + "device-created": "Dispositivo criado", "confirm-regen-covers": "", - "alert-long-running": "", - "confirm-delete-multiple-series": "", - "confirm-delete-series": "", - "alert-bad-theme": "", - "confirm-library-delete": "", + "alert-long-running": "Isto é um processo de longa duração. Por favor espere o tempo necessário para este terminar antes de o invocar novamente.", + "confirm-delete-multiple-series": "Tem a certeza que deseja eliminar {{count}} séries? Os ficheiros em disco não serão alterados.", + "confirm-delete-series": "Tem a certeza que deseja eliminar esta série? Os ficheiros em disco não serão alterados.", + "alert-bad-theme": "Existe um css inválido ou inseguro no tema. Por favor contacte o administrador para corrigir este problema. Irá ser usado o tema por defeito.", + "confirm-library-delete": "Tem a certeza que deseja eliminar a biblioteca {{name}}? Esta ação não pode ser revertida.", "confirm-library-type-change": "", - "confirm-download-size": "" + "confirm-download-size": "", + "list-doesnt-exist": "Esta lista não existe" }, "actionable": { - "scan-library": "", - "refresh-covers": "", - "analyze-files": "", - "settings": "", - "edit": "", - "mark-as-read": "", - "mark-as-unread": "", - "scan-series": "", + "scan-library": "Analisar Biblioteca", + "refresh-covers": "Atualizar Capas", + "analyze-files": "Analisar Ficheiros", + "settings": "Definições", + "edit": "Editar", + "mark-as-read": "Marcar como Lido", + "mark-as-unread": "Marcar como Não Lido", + "scan-series": "Analisar Séries", "add-to": "", - "add-to-want-to-read": "", - "remove-from-want-to-read": "", + "add-to-want-to-read": "Adicionar a Leituras Futuras", + "remove-from-want-to-read": "Remover de Leituras Futuras", "remove-from-on-deck": "", - "others": "", - "add-to-reading-list": "", - "add-to-collection": "", - "send-to": "", - "delete": "", - "download": "", + "others": "Outros", + "add-to-reading-list": "Adicionar a Lista de Leitura", + "add-to-collection": "Adicionar a Coleção", + "send-to": "Enviar Para", + "delete": "Eliminar", + "download": "Descarregar", "read-incognito": "", - "details": "", - "view-series": "", - "clear": "", - "import-cbl": "" + "details": "Detalhes", + "view-series": "Ver Séries", + "clear": "Limpar", + "import-cbl": "Importar CBL", + "read": "Ler" }, "preferences": { - "left-to-right": "", - "right-to-left": "", - "horizontal": "", - "vertical": "", - "automatic": "", - "fit-to-height": "", - "fit-to-width": "", - "original": "", - "fit-to-screen": "", + "left-to-right": "Esquerda para Direita", + "right-to-left": "Direita para Esquerda", + "horizontal": "Horizontal", + "vertical": "Vertical", + "automatic": "Automático", + "fit-to-height": "Ajustar à Altura", + "fit-to-width": "Ajustar à Largura", + "original": "Original", + "fit-to-screen": "Ajustar ao Ecrã", "no-split": "", - "webtoon": "", + "webtoon": "Webtoon", "single": "", "double": "", "double-manga": "", "scroll": "", - "1-column": "", - "2-column": "", + "1-column": "1 Coluna", + "2-column": "2 Colunas", "cards": "", - "list": "", - "up-to-down": "" + "list": "Lista", + "up-to-down": "Cima para Baixo" }, "validation": { - "required-field": "", - "valid-email": "", - "password-validation": "" + "required-field": "Este campo é obrigatório", + "valid-email": "Este deve ser um email válido", + "password-validation": "A palavra passe tem de ter entre 6 a 32 caracteres" }, "entity-type": { - "volume": "", - "chapter": "", - "series": "", - "bookmark": "", - "logs": "" + "volume": "volume", + "chapter": "capítulo", + "series": "séries", + "bookmark": "marcador", + "logs": "logs" }, "common": { "reset-to-default": "", - "close": "", - "cancel": "", - "create": "", - "save": "", - "reset": "", - "add": "", - "apply": "", - "delete": "", - "edit": "", - "help": "", + "close": "Fechar", + "cancel": "Cancelar", + "create": "Criar", + "save": "Guardar", + "reset": "Repor", + "add": "Adicionar", + "apply": "Aplicar", + "delete": "Eliminar", + "edit": "Editar", + "help": "Ajuda", "submit": "", - "email": "", - "read": "", - "loading": "", - "username": "", - "password": "", - "promoted": "", - "select-all": "", - "deselect-all": "", - "series-count": "", - "item-count": "", - "book-num": "", - "issue-hash-num": "", - "issue-num": "", - "chapter-num": "", - "volume-num": "" + "email": "Email", + "read": "Ler", + "loading": "A Carregar…", + "username": "Nome de utilizador", + "password": "Palavra passe", + "promoted": "Promovido", + "select-all": "Selecionar Todos", + "deselect-all": "Desselecionar Todos", + "series-count": "{{num}} Séries", + "item-count": "{{num}} Itens", + "book-num": "Livro", + "issue-hash-num": "Número #", + "issue-num": "Número", + "chapter-num": "Capítulo", + "volume-num": "Volume" + }, + "cover-image-size": { + "default": "Padrão (320x455)", + "medium": "Médio (640x909)", + "xlarge": "Muito Grande (1265x1795)", + "large": "Grande (900x1277)" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter": "Capítulo Anterior", + "continuous-reading-next-chapter": "Capítulo Seguinte" + }, + "filter-field-pipe": { + "age-rating": "Classificação Etária", + "cover-artist": "Artista de Capa", + "formats": "Formatos", + "genres": "Géneros", + "libraries": "Bibliotecas", + "summary": "Sumário", + "series-name": "Nome da Série", + "translators": "Tradutores", + "characters": "Personagens", + "languages": "Idiomas", + "colorist": "Colorista", + "editor": "Editor", + "inker": "Arte-Finalista", + "letterer": "Letrista", + "publication-status": "Estado de Publicação", + "penciller": "Desenhista", + "publisher": "Editora", + "read-time": "Tempo de Leitura", + "release-year": "Ano de Lançamento", + "tags": "Etiquetas", + "user-rating": "Classificação do Utilizador", + "writers": "Escritores" + }, + "metadata-builder": { + "remove-rule": "Remover Linha", + "add-rule": "Adicionar Regra" } } diff --git a/UI/Web/src/assets/langs/pt_BR.json b/UI/Web/src/assets/langs/pt_BR.json new file mode 100644 index 0000000000..a4365381c2 --- /dev/null +++ b/UI/Web/src/assets/langs/pt_BR.json @@ -0,0 +1,1755 @@ +{ + "login": { + "title": "Entrar", + "username": "{{common.username}}", + "password": "{{common.password}}", + "password-validation": "{{validation.password-validation}}", + "forgot-password": "Esqueceu sua senha?", + "submit": "{{common.submit}}" + }, + "dashboard": { + "no-libraries": "Ainda não há bibliotecas configuradas. Configurar alguns em", + "server-settings-link": "Configurações do servidor", + "not-granted": "Você não recebeu acesso a nenhuma biblioteca.", + "on-deck-title": "Na Estante", + "recently-updated-title": "Séries Atualizadas Recentemente", + "recently-added-title": "Séries Recém-Adicionadas" + }, + "edit-user": { + "edit": "{{common.edit}}", + "close": "{{common.close}}", + "username": "{{common.username}}", + "required": "{{validation.required-field}}", + "email": "{{common.email}}", + "not-valid-email": "{{validation.valid-email}}", + "cancel": "{{common.cancel}}", + "saving": "Salvando…", + "update": "Atualizar" + }, + "user-scrobble-history": { + "title": "Histórico de scrobble", + "description": "Aqui você encontrará todos os eventos scrobble vinculados à sua conta. Para que os eventos existam, você deve ter um provedor scrobble ativo configurado. Todos os eventos processados serão apagados após um mês. Se houver eventos não processados, é provável que eles não possam formar correspondências upstream. Entre em contato com seu administrador para corrigi-los.", + "filter-label": "Filtro", + "created-header": "Criado", + "last-modified-header": "Última Modificação", + "type-header": "Tipo", + "series-header": "Séries", + "data-header": "Dados", + "is-processed-header": "É Processado", + "no-data": "Sem Dados", + "volume-and-chapter-num": "Volume {{v}} Capítulo {{n}}", + "rating": "Avaliação {{r}}", + "not-applicable": "Não Aplicável", + "processed": "Processado", + "not-processed": "Não Processado" + }, + "scrobble-event-type-pipe": { + "chapter-read": "Progresso da Leitura", + "score-updated": "Atualizar Avaliação", + "want-to-read-add": "Quero Ler: Adicionar", + "want-to-read-remove": "Quero Ler: Remover", + "review": "Atualizar Análise" + }, + "spoiler": { + "click-to-show": "Spoiler, clique para mostrar" + }, + "review-series-modal": { + "title": "Editar Análise", + "tagline-label": "Slogan", + "review-label": "Análise", + "close": "{{common.close}}", + "save": "{{common.save}}" + }, + "review-card-modal": { + "close": "{{common.close}}", + "user-review": "{{username}}'s Análise", + "external-mod": "(externo)", + "go-to-review": "Ir Para a Análise" + }, + "review-card": { + "your-review": "Esta é sua análise", + "external-review": "Análise Externa", + "local-review": "Análise", + "rating-percentage": "Avaliação {{r}}%" + }, + "want-to-read": { + "title": "Quero Ler", + "series-count": "{{common.series-count}}", + "no-items": "Não há itens. Tente adicionar uma série.", + "no-items-filtered": "Nenhum item corresponde ao seu filtro atual." + }, + "user-preferences": { + "title": "Painel do Usuário", + "pref-description": "Essas são configurações globais vinculadas à sua conta.", + "account-tab": "Conta", + "preferences-tab": "Preferências", + "3rd-party-clients-tab": "Clientes de Terceiros", + "theme-tab": "Tema", + "devices-tab": "Dispositivos", + "stats-tab": "Estatísticas", + "scrobbling-tab": "Scrobbling", + "success-toast": "Preferências do usuário atualizadas", + "global-settings-title": "Configurações Globais", + "page-layout-mode-label": "Modo de Layout de Página", + "page-layout-mode-tooltip": "Mostrar itens como cartões ou exibição de lista na página Detalhes da série.", + "locale-label": "Idioma", + "locale-tooltip": "A linguagem que Kavita deve usar", + "blur-unread-summaries-label": "Desfocar Resumos Não Lidos", + "blur-unread-summaries-tooltip": "Desfoca o texto do resumo em volumes ou capítulos que não têm progresso de leitura (para evitar spoilers)", + "prompt-on-download-label": "Perguntar ao Baixar", + "prompt-on-download-tooltip": "Avisar quando um download excede {{size}} MB de tamanho", + "disable-animations-label": "Desabilitar Animações", + "disable-animations-tooltip": "Desabilitar animações no site. Útil para leitores e-ink.", + "collapse-series-relationships-label": "Agrupar Relações das Séries", + "collapse-series-relationships-tooltip": "Kavita deve mostrar uma série que não tem relacionamentos ou é derivado/prequela", + "share-series-reviews-label": "Compartilhar Análises de Séries", + "share-series-reviews-tooltip": "Kavita deve incluir suas análises da série para outros usuários", + "image-reader-settings-title": "Leitor de Imagem", + "reading-direction-label": "Direção de Leitura", + "reading-direction-tooltip": "Direção para clicar para mover para a próxima página. Da direita para a esquerda significa que você clica no lado esquerdo da tela para ir para a próxima página.", + "scaling-option-label": "Opções de Escala", + "scaling-option-tooltip": "Como dimensionar a imagem para sua tela.", + "page-splitting-label": "Divisão de Página", + "page-splitting-tooltip": "Como dividir uma imagem de largura total (ou seja, as imagens esquerda e direita são combinadas)", + "reading-mode-label": "Modo de Leitura", + "layout-mode-label": "Modo de Layout", + "layout-mode-tooltip": "Renderiza uma única imagem na tela ou duas imagens lado a lado", + "background-color-label": "Cor de Fundo", + "auto-close-menu-label": "Fechar Automaticamente o Menu", + "show-screen-hints-label": "Mostrar Dicas na Tela", + "emulate-comic-book-label": "Emular quadrinhos", + "swipe-to-paginate-label": "Deslizar para Paginar", + "book-reader-settings-title": "Leitor de Livro", + "tap-to-paginate-label": "Toque para Paginar", + "tap-to-paginate-tooltip": "Se as laterais da tela do leitor de livros permitirem tocar nela para ir para a página anterior/próxima", + "immersive-mode-label": "Modo Imersivo", + "immersive-mode-tooltip": "Isso ocultará o menu atrás de um clique no documento do leitor e gire o toque para paginar", + "reading-direction-book-label": "Direção de Leitura", + "reading-direction-book-tooltip": "Direção para clicar para ir para a próxima página. Da direita para a esquerda significa que você clica no lado esquerdo da tela para ir para a próxima página.", + "font-family-label": "Família da Fonte", + "font-family-tooltip": "Família de fontes para carregar. Padrão carregará a fonte padrão do livro", + "writing-style-label": "Estilo de Escrita", + "writing-style-tooltip": "Altera a direção do texto. Horizontal é da esquerda para a direita, vertical é de cima para baixo.", + "layout-mode-book-label": "Modo de Layout", + "layout-mode-book-tooltip": "Como o conteúdo deve ser apresentado. Rolar é como o livro o embala. 1 ou 2 colunas se ajustam à altura do dispositivo e 1 ou 2 colunas de texto por página", + "color-theme-book-label": "Cor do Tema", + "color-theme-book-tooltip": "Qual tema de cor aplicar ao conteúdo e ao menu do leitor de livros", + "font-size-book-label": "Tamanho da Fonte", + "line-height-book-label": "Espaçamento da Linha", + "line-height-book-tooltip": "Quanto espaçamento entre as linhas do livro", + "margin-book-label": "Margem", + "margin-book-tooltip": "Quanto espaçamento em cada lado da tela. Isso será substituído por 0 em dispositivos móveis, independentemente dessa configuração.", + "clients-opds-alert": "O OPDS não está ativado neste servidor. Isso não afetará os usuários do Tachiyomi.", + "clients-opds-description": "Todos os clientes de terceiros usarão a chave de API ou o URL de conexão abaixo. Estas são como senhas, mantenha-as privadas.", + "clients-api-key-tooltip": "A chave API é como uma senha. Mantenha isso em segredo, mantenha-o seguro.", + "clients-opds-url-tooltip": "URL OPDS", + "reset": "{{common.reset}}", + "save": "{{common.save}}" + }, + "user-holds": { + "title": "Segurar Scrobble", + "description": "Esta é uma lista de séries gerenciada pelo usuário que não será scrobbled para provedores upstream. Você pode remover uma série a qualquer momento e o próximo evento compatível com Scrobble (progresso de leitura, classificação, status de desejo de leitura) acionará eventos." + }, + "theme-manager": { + "title": "Gerenciador de Temas", + "looking-for-theme": "Procurando um tema de claro ou e-ink? Temos alguns temas personalizados que você pode usar em nosso ", + "looking-for-theme-continued": "tema no github.", + "scan": "Escanear", + "site-themes": "Temas do Site", + "set-default": "Definir Padrão", + "apply": "{{common.apply}}", + "applied": "Aplicado", + "updated-toastr": "O padrão do site foi atualizado para {{name}}", + "scan-queued": "Uma varredura do tema do site foi colocada na fila" + }, + "theme": { + "theme-dark": "Escuro", + "theme-black": "Preto", + "theme-paper": "Papel", + "theme-white": "Branco" + }, + "restriction-selector": { + "title": "Restrição de Classificação Etária", + "description": "Quando selecionado, todas as séries e listas de leitura que tiverem pelo menos um item maior que a restrição selecionada serão removidas dos resultados.", + "not-applicable-for-admins": "Isso não é aplicável para administradores.", + "age-rating-label": "Classificação Etária", + "no-restriction": "Sem Restrição", + "include-unknowns-label": "Incluir Desconhecidos", + "include-unknowns-tooltip": "se verdadeiro, desconhecidos serão permitidos com restrição de idade. Isso pode levar ao vazamento de mídia não marcada para usuários com restrições de idade." + }, + "site-theme-provider-pipe": { + "system": "Sistema", + "user": "Usuário" + }, + "manage-devices": { + "title": "Gerenciador de Dispositivo", + "description": "Esta seção é para você configurar dispositivos que não podem se conectar ao Kavita por meio de um navegador da Web e, em vez disso, possuem um endereço de e-mail que aceita arquivos.", + "devices-title": "Dispositivos", + "no-devices": "Ainda não há dispositivos configurados", + "platform-label": "Platforma: ", + "email-label": "Email: ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" + }, + "edit-device": { + "device-name-label": "Nome do Dispositivo", + "email-label": "{{common.email}}", + "email-tooltip": "Este e-mail será usado para aceitar o arquivo via Enviar Para", + "device-platform-label": "Plataforma do Dispositivo", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" + }, + "change-password": { + "password-label": "{{common.password}}", + "current-password-label": "Senha Atual", + "new-password-label": "Nova Senha", + "confirm-password-label": "Confirmar Senha", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "As senhas devem corresponder", + "permission-error": "Você não tem permissão para alterar sua senha. Entre em contato com o administrador do servidor." + }, + "change-email": { + "email-label": "{{common.email}}", + "current-password-label": "Senha Atual", + "email-not-confirmed": "Este e-mail não está confirmado", + "email-updated-title": "E-mail atualizado", + "email-updated-description": "Você pode usar o seguinte link abaixo para confirmar o e-mail da sua conta. Se o seu servidor estiver acessível externamente, um e-mail será enviado para o e-mail e o link poderá ser usado para confirmar o e-mail.", + "setup-user-account": "Configurar conta de usuário", + "invite-url-label": "URL de Convite", + "invite-url-tooltip": "Copie isso e cole em uma nova aba", + "permission-error": "Você não tem permissão para alterar seu e-mail. Entre em contato com o administrador do servidor.", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "change-age-restriction": { + "age-restriction-label": "Classificação Etária", + "unknowns": "Desconhecidos", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "api-key": { + "copy": "Copiar", + "regen-warning": "Gerar novamente sua chave de API invalidará todos os clientes existentes.", + "no-key": "ERRO - NENHUMA CHAVE DEFINIDA", + "confirm-reset": "Isso invalidará todas as configurações OPDS que você configurou. Você tem certeza que quer continuar?", + "key-reset": "Redefinir chave API", + "show": "Mostrar" + }, + "scrobbling-providers": { + "title": "Provedores de Scrobbling", + "requires": "Este recurso requer uma licença ativa do {{product}}", + "token-expired": "Token Expirado", + "no-token-set": "Nenhum Token Definido", + "token-set": "Definir Token", + "generate": "Gerar", + "instructions": "Os usuários iniciantes devem clicar em '{{scrobbling-providers.generate}}' abaixo para permitir que Kavita+ fale com {{service}}. Depois de autorizar o programa, copie e cole o token na entrada abaixo. Você pode regenerar seu token a qualquer momento.", + "token-input-label": "{{service}} token vai aqui", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "typeahead": { + "locked-field": "O campo está bloqueado", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "add-item": "Adicionar {{item}}…", + "no-data": "Sem dados", + "add-custom-item": ", digite para adicionar um item personalizado" + }, + "generic-list-modal": { + "close": "{{common.close}}", + "clear": "Limpar", + "filter": "Filtrar", + "open-filtered-search": "Abre uma pesquisa filtrada para {{item}}" + }, + "user-stats-info-cards": { + "total-pages-read-label": "Total de Páginas Lidas", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", + "total-words-read-label": "Total de Palavras Lidas", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "Total de Tempo Lendo", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "Capítulos Lidos", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "avg-reading-per-week-label": "Média de Leitura por Semana", + "last-active-label": "Ativo pela Última Vez", + "chapters": "{{value}} capítulos" + }, + "user-stats": { + "library-read-progress-title": "Progresso na Leitura da Biblioteca", + "read-percentage": "% Lido" + }, + "top-readers": { + "title": "Melhores Leitores", + "time-selection-label": "Prazo", + "comics-label": "Quadrinhos: {{value}} hrs", + "manga-label": "Mangá: {{value}} hrs", + "books-label": "Livros: {{value}} hrs", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" + }, + "role-selector": { + "title": "Papéis" + }, + "directory-picker": { + "title": "Escolher um Diretório", + "close": "{{common.close}}", + "path-label": "Caminho", + "path-placeholder": "Comece a digitar ou selecione o caminho", + "instructions": "Selecione uma pasta para visualizar o breadcrumb. Não vê seu diretório? Tente verificar primeiro /.", + "type-header": "Tipo", + "name-header": "Nome", + "cancel": "{{common.cancel}}", + "share": "Compartilhar", + "help": "{{common.help}}" + }, + "library-access-modal": { + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "Acesso à Biblioteca", + "close": "{{common.close}}", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "no-data": "Ainda não há bibliotecas configuradas." + }, + "time-periods": { + "this-week": "Esta Semana", + "last-7-days": "Últimos 7 Dias", + "last-30-days": "Últimos 30 Dias", + "last-90-days": "Últimos 90 Dias", + "last-year": "Último Ano", + "all-time": "Todo o Tempo" + }, + "device-platform-pipe": { + "custom": "Personalizar" + }, + "day-of-week-pipe": { + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday": "Sábado", + "sunday": "Domingo" + }, + "cbl-import-result-pipe": { + "success": "Sucesso", + "partial": "Parcial", + "failure": "Falha" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "Sua conta não tem acesso a todas as séries na lista ou Kavita não tem nada presente na lista.", + "chapter-missing": "{{serie}}: Capítulo {{chapter}} está faltando em Kavita. Este item será ignorado.", + "empty-file": "O arquivo cbl está vazio, nada a ser feito.", + "name-conflict": "Já existe uma lista de leitura ({{readingListName}}) em sua conta que corresponde ao arquivo cbl.", + "series-collision": "A série, {{seriesLink}}, colide com outra série de mesmo nome em outra biblioteca.", + "series-missing": "A série, {{series}}, está faltando no Kavita ou sua conta não tem permissão. Todos os itens com esta série serão ignorados na importação.", + "volume-missing": "{{series}}: O volume {{volume}} está faltando no Kavita ou sua conta não tem permissão. Todos os itens com este número de volume serão ignorados.", + "all-chapter-missing": "Todos os capítulos não podem corresponder aos Capítulos em Kavita.", + "invalid-file": "O arquivo está corrompido ou não corresponde às tags/especificações esperadas.", + "success": "{{series}} volume {{volume}} capítulo {{chapter}} mapeado com sucesso." + }, + "time-duration-pipe": { + "hours": "{{value}} horas", + "minutes": "{{value}} minutos", + "days": "{{value}} dias", + "months": "{{value}} meses", + "years": "{{value}} anos" + }, + "time-ago-pipe": { + "never": "Nunca", + "just-now": "só agora", + "min-ago": "um minuto atrás", + "mins-ago": "{{value}} minutos atrás", + "hour-ago": "uma hora atrás", + "hours-ago": "{{value}} horas atrás", + "day-ago": "um dia atrás", + "days-ago": "{{value}} dias atrás", + "month-ago": "um mês atrás", + "months-ago": "{{value}} meses atrás", + "year-ago": "um ano atrás", + "years-ago": "{{value}} anos atrás" + }, + "relationship-pipe": { + "adaptation": "Adaptação", + "alternative-setting": "Configuração Alternativa", + "alternative-version": "Versão Alternativa", + "character": "Personagem", + "contains": "Contém", + "doujinshi": "Doujinshi", + "other": "Outro", + "prequel": "Prequela", + "sequel": "Sequência", + "side-story": "História Paralela", + "spin-off": "Derivado", + "parent": "Origem", + "edition": "Edição" + }, + "publication-status-pipe": { + "ongoing": "Em Andamento", + "hiatus": "Hiato", + "completed": "Completado", + "cancelled": "Cancelado", + "ended": "Concluído" + }, + "person-role-pipe": { + "artist": "Artista", + "character": "Personagem", + "colorist": "Colorista", + "cover-artist": "Artista da Capa", + "editor": "Editor", + "inker": "Arte-finalista", + "letterer": "Letrista", + "penciller": "Desenhista", + "publisher": "Editora", + "writer": "Escritor", + "other": "Outro" + }, + "manga-format-pipe": { + "epub": "EPUB", + "archive": "Arquivo", + "image": "Imagem", + "pdf": "PDF", + "unknown": "Desconhecido" + }, + "library-type-pipe": { + "book": "Livro", + "comic": "Quadrinhos", + "manga": "Mangá" + }, + "age-rating-pipe": { + "unknown": "Desconhecido", + "early-childhood": "Primeira Infância", + "adults-only": "Somente adultos maiores de 18 anos", + "everyone": "Todos", + "everyone-10-plus": "Todos maiores de 10", + "g": "G", + "kids-to-adults": "Crianças a Adultos", + "mature": "Maduro", + "ma15-plus": "MA15+", + "mature-17-plus": "Maduro 17+", + "rating-pending": "Avaliação Pendente", + "teen": "Jovem", + "x18-plus": "X18+", + "not-applicable": "Não Aplicável", + "pg": "PG", + "r18-plus": "R18+" + }, + "reset-password": { + "title": "Redefinir Senha", + "description": "Digite o e-mail da sua conta. O Kavita enviará um e-mail se for válido no arquivo, caso contrário, peça ao administrador o link dos registros.", + "email-label": "{{common.email}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "submit": "{{common.submit}}" + }, + "reset-password-modal": { + "title": "Redefinir {{username}}'s Senha", + "new-password-label": "Nova Senha", + "error-label": "Erro: ", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "all-series": { + "series-count": "{{common.series-count}}", + "title": "Todas as Séries" + }, + "announcements": { + "title": "Anúncios" + }, + "changelog": { + "installed": "Instalado", + "download": "Baixar", + "published-label": "Publicado: ", + "available": "Disponível", + "description": "se você não vir um {{installed}}", + "description-continued": "tag, você está em um lançamento nightly . Somente as versões principais serão exibidas como disponíveis." + }, + "invite-user": { + "title": "Convidar Usuário", + "close": "{{common.close}}", + "description": "Convide um usuário para o seu servidor. Digite seu e-mail e enviaremos um e-mail para criar uma conta. Se você não quiser usar nosso serviço de e-mail, você pode um hospedar seu próprio serviço de e-mail ou usar um e-mail falso (Esqueceu o usuário não funcionará). Um link será apresentado independentemente e pode ser usado para configurar a conta manualmente.", + "email": "{{common.email}}", + "required-field": "{{common.required-field}}", + "setup-user-title": "Usuário Convidado", + "setup-user-description": "Você pode usar o seguinte link abaixo para configurar a conta para seu usuário ou usar o botão copiar. Você pode precisar sair antes de usar o link para registrar um novo usuário. Se o seu servidor estiver acessível externamente, um e-mail será enviado ao usuário e os links poderão ser usados por ele para concluir a configuração de sua conta.", + "setup-user-account": "Configurar conta de usuário", + "setup-user-account-tooltip": "Copie isso e cole em uma nova aba. Você pode precisar fazer logout.", + "invite-url-label": "URL do convite", + "invite": "Convite", + "inviting": "Convidando…", + "cancel": "{{common.cancel}}" + }, + "library-selector": { + "title": "Bibliotecas", + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "no-data": "Ainda não há bibliotecas configuradas." + }, + "license": { + "title": "Licença Kavita+", + "manage": "Gerenciar", + "invalid-license-tooltip": "se sua assinatura terminou, você deve enviar um e-mail para o suporte para criar uma nova assinatura", + "check": "Checar", + "cancel": "{{common.cancel}}", + "edit": "{{common.edit}}", + "buy": "Comprar", + "activate": "Ativar", + "renew": "Renovar", + "no-license-key": "Sem chave de licença", + "license-valid": "Licença é Válida", + "license-not-valid": "Licença Não é Válida", + "loading": "{{common.loading}}", + "activate-description": "Insira a chave de licença e o e-mail usados para se registrar no Stripe", + "activate-license-label": "Chave de licença", + "activate-email-label": "{{common.email}}", + "activate-delete": "Excluir", + "activate-save": "{{common.save}}" + }, + "book-line-overlay": { + "copy": "Copiar", + "bookmark": "Marcador", + "close": "{{common.close}}", + "required-field": "{{common.required-field}}", + "bookmark-label": "Nome do Marcador", + "save": "{{common.save}}" + }, + "book-reader": { + "title": "Configurações para Livros", + "page-label": "Página", + "pagination-header": "Seção", + "go-to-page": "Ir para página", + "go-to-last-page": "Ir para a última página", + "prev-page": "Página Anterior", + "next-page": "Página Seguinte", + "prev-chapter": "Capítulo/Volume Anterior", + "next-chapter": "Capítulo/Volume Seguinte", + "skip-header": "Ir para o conteúdo principal", + "virtual-pages": "páginas virtuais", + "settings-header": "Configurações", + "table-of-contents-header": "Índice", + "bookmarks-header": "Marcadores", + "toc-header": "Índice", + "loading-book": "Carregando livro…", + "go-back": "Voltar", + "incognito-mode-alt": "O modo de navegação anônima está ativado. Alterne para desligar.", + "incognito-mode-label": "Modo Incógnito", + "next": "Seguinte", + "previous": "Anterior", + "go-to-page-prompt": "Existem {{totalPages}} páginas. Para qual página você quer ir?" + }, + "personal-table-of-contents": { + "no-data": "Nada Marcado Ainda", + "page": "Página {{value}}", + "delete": "Excluir {{bookmarkName}}" + }, + "confirm-email": { + "title": "Registrar", + "description": "Preencha o formulário para concluir seu cadastro", + "error-label": "Erros: ", + "username-label": "{{common.username}}", + "password-label": "{{common.password}}", + "email-label": "{{common.email}}", + "required-field": "{{common.required-field}}", + "valid-email": "{{common.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "Registrar" + }, + "confirm-email-change": { + "title": "Validar alteração de e-mail", + "non-confirm-description": "Aguarde enquanto a atualização do seu e-mail é validada.", + "confirm-description": "Seu e-mail foi validado e agora foi alterado no Kavita. Você será redirecionado para o login.", + "success": "Sucesso!" + }, + "confirm-reset-password": { + "title": "Redefinir Senha", + "description": "Digite a nova senha", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}" + }, + "register": { + "title": "Registrar", + "description": "Preencha o formulário para registrar uma conta de administrador", + "username-label": "{{common.username}}", + "email-label": "{{common.email}}", + "email-tooltip": "O e-mail não precisa ser um endereço real, mas fornece acesso à senha esquecida. Ele não é enviado para fora do servidor, a menos que a senha esquecida seja usada sem um host de serviço de e-mail personalizado.", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "Registrar" + }, + "series-detail": { + "page-settings-title": "Configurações da Página", + "close": "{{common.close}}", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-option-card": "Cartão", + "layout-mode-option-list": "Lista", + "continue-from": "Continuar {{title}}", + "read": "{{common.read}}", + "continue": "Continuar", + "read-options-alt": "Opções de leitura", + "incognito": "Incógnito", + "remove-from-want-to-read": "Remover do Quero Ler", + "add-to-want-to-read": "Adicionar ao Quero Ler", + "edit-series-alt": "Editar Informações da Série", + "download-series--tooltip": "Baixar Séries", + "downloading-status": "Baixando…", + "user-reviews-alt": "Análises do Usuário", + "storyline-tab": "Enredo", + "books-tab": "Livros", + "volumes-tab": "Volumes", + "specials-tab": "Especiais", + "related-tab": "Relacionado", + "recommendations-tab": "Recomendações", + "send-to": "Arquivo enviado por e-mail para {{deviceName}}", + "no-pages": "{{toasts.no-pages}}", + "no-chapters": "Não há capítulos neste volume. Não posso ler.", + "cover-change": "Pode levar até um minuto para o seu navegador atualizar a imagem. Até lá, a imagem antiga pode ser exibida em algumas páginas." + }, + "series-metadata-detail": { + "links-title": "Links", + "genres-title": "Gêneros", + "tags-title": "Tags", + "collections-title": "{{side-nav.collections}}", + "reading-lists-title": "{{side-nav.reading-lists}}", + "writers-title": "Escritores/Autores", + "cover-artists-title": "Artistas de Capa", + "characters-title": "Personagens", + "colorists-title": "Coloristas", + "editors-title": "Editores", + "inkers-title": "Arte-finalistas", + "letterers-title": "Letristas", + "translators-title": "Tradutores", + "pencillers-title": "Desenhistas", + "publishers-title": "Editoras", + "promoted": "{{common.promoted}}", + "see-more": "Veja Mais", + "see-less": "Veja Menos" + }, + "badge-expander": { + "more-items": "e {{count}} mais" + }, + "read-more": { + "read-more": "Leia Mais", + "read-less": "Leia Menos" + }, + "update-notification-modal": { + "title": "Nova Atualização Disponível!", + "close": "{{common.close}}", + "help": "Como Atualizar", + "download": "Baixar" + }, + "side-nav-companion-bar": { + "page-settings-title": "{{series-detail.page-settings-title}}", + "open-filter-and-sort": "Abrir Filtragem e Classificação", + "close-filter-and-sort": "Fechar Filtragem e Classificação", + "filter-and-sort-alt": "Classificar / Filtrar" + }, + "side-nav": { + "home": "Página Inicial", + "want-to-read": "Quero Ler", + "collections": "Coleções", + "reading-lists": "Listas de Leitura", + "bookmarks": "Marcadores", + "filter-label": "Filtro", + "all-series": "Todas as Séries", + "clear": "Limpar", + "donate": "Doar" + }, + "library-settings-modal": { + "close": "{{common.close}}", + "edit-title": "Editar {{name}}", + "add-title": "Adicionar Biblioteca", + "general-tab": "Geral", + "folder-tab": "Pasta", + "cover-tab": "Capa", + "advanced-tab": "Avançado", + "name-label": "Nome", + "library-name-unique": "O nome da biblioteca deve ser exclusivo", + "last-scanned-label": "Último Escaneamento:", + "type-label": "Tipo", + "type-tooltip": "O tipo de biblioteca determina como os nomes de arquivo são analisados e se a IU mostra Capítulos (Mangá) versus Números (Quadrinhos). O livro funciona da mesma maneira que o mangá, mas tem nomenclatura diferente na interface do usuário.", + "folder-description": "Adicionar pastas à sua biblioteca", + "browse": "Navegar por Pastas de Mídia", + "help-us-part-1": "Ajude-nos seguindo ", + "help-us-part-2": "nosso guia", + "help-us-part-3": "para nomear e organizar sua mídia.", + "naming-conventions-part-1": "Kavita tem ", + "naming-conventions-part-2": "requisitos para pastas.", + "naming-conventions-part-3": "Verifique este link para garantir que você está seguindo, caso contrário, os arquivos podem não aparecer na verificação.", + "cover-description": "Ícones de imagem de biblioteca personalizados são opcionais", + "cover-description-extra": "A imagem da biblioteca não deve ser grande. Apontar para um arquivo pequeno, 32x32 pixels de tamanho. Kavita não executa validação de tamanho.", + "manage-collection-label": "Gerenciar Coleções", + "manage-collection-tooltip": "Kavita deve criar coleções a partir de tags SeriesGroup encontradas nos arquivos ComicInfo.xml/opf", + "manage-reading-list-label": "Gerenciar Listas de Leituras", + "manage-reading-list-tooltip": "Kavita deve criar listas de leitura a partir das tags StoryArc/StoryArcNumber e AlternativeSeries/AlternativeCount encontradas nos arquivos ComicInfo.xml/opf", + "allow-scrobbling-label": "Permitir Scrobbling", + "allow-scrobbling-tooltip": "Caso o Kavita faça scrobble de eventos de leitura, status de Quero Ler, as avaliações e as análises dos provedores configurados. Isso só ocorrerá se o servidor tiver uma assinatura Kavita+ ativa.", + "folder-watching-label": "Monitorar Pasta", + "folder-watching-tooltip": "Substitua a pasta do servidor observando esta biblioteca. Se desativado, a observação de pastas não será executada nas pastas que esta biblioteca contém. Se as bibliotecas compartilharem pastas, as pastas ainda poderão ser executadas.", + "include-in-dashboard-label": "Incluir no Painel", + "include-in-dashboard-tooltip": "Caso séries da biblioteca sejam incluídas no Painel. Isso afeta todos os fluxos, como Na Estante, Atualizado Recentemente, Adicionado Recentemente ou quaisquer adições personalizadas.", + "include-in-recommendation-label": "Incluir em Recomendado", + "include-in-recommendation-tooltip": "As séries da biblioteca devem ser incluídas na página Recomendado.", + "include-in-search-label": "Incluir na Pesquisa", + "include-in-search-tooltip": "As séries e quaisquer informações derivadas (gêneros, pessoas, arquivos) da biblioteca devem ser incluídas nos resultados da pesquisa.", + "force-scan": "Forçar Escaneamento", + "force-scan-tooltip": "Isso forçará uma escaneamento na biblioteca, tratando como um novo escaneamento", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "next": "Seguinte", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}" + }, + "reader-settings": { + "general-settings-title": "Configurações Gerais", + "font-family-label": "{{user-preferences.font-family-label}}", + "font-size-label": "{{user-preferences.font-size-book-label}}", + "line-spacing-label": "{{user-preferences.line-height-book-label}}", + "margin-label": "{{user-preferences.margin-book-label}}", + "reset-to-defaults": "Redefinir para os Padrões", + "reader-settings-title": "Configurações do Leitor", + "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "right-to-left": "Direita para Esquerda", + "left-to-right": "Esquerda para Direita", + "horizontal": "Horizontal", + "vertical": "Vertical", + "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-tooltip": "Altera a direção do texto. Horizontal é da esquerda para a direita, vertical é de cima para baixo.", + "tap-to-paginate-label": "Toque em Paginação", + "tap-to-paginate-tooltip": "Clique nas bordas da tela para paginar", + "on": "Ligado", + "off": "Desligado", + "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-tooltip": "Isso ocultará o menu atrás de um clique no documento do leitor e gire o toque para paginar", + "fullscreen-label": "Tela Cheia", + "fullscreen-tooltip": "Põe o leitor no modo de tela cheia", + "exit": "Sair", + "enter": "Entrar", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-tooltip": "Scroll: Espelha o arquivo epub (geralmente uma longa página de rolagem por capítulo).
    1 Coluna: Cria uma única página virtual por vez.
    2 Colunas: Cria duas páginas virtuais por vez dispostas lado a lado.", + "layout-mode-option-scroll": "Rolar", + "layout-mode-option-1col": "1 Coluna", + "layout-mode-option-2col": "2 Colunas", + "color-theme-title": "Cor do Tema", + "theme-dark": "Escuro", + "theme-black": "Preto", + "theme-white": "Branco", + "theme-paper": "Papel" + }, + "table-of-contents": { + "no-data": "Este livro não tem um índice definido nos metadados ou um arquivo toc" + }, + "bookmarks": { + "title": "{{side-nav.bookmarks}}", + "series-count": "{{common.series-count}}", + "no-data": "Não há marcadores. Tente criar", + "no-data-2": "um.", + "confirm-delete": "Tem certeza de que deseja limpar todos os marcadores de várias séries? Isto não pode ser desfeito.", + "confirm-single-delete": "Tem certeza de que deseja limpar todos os marcadores de {{seriesName}}? Isto não pode ser desfeito.", + "delete-success": "Os marcadores foram removidos", + "delete-single-success": "Os marcadores de {{seriesName}} foram removidos" + }, + "bulk-operations": { + "title": "Ações em Massa", + "items-selected": "{{num}} itens selecionados", + "mark-as-unread": "Marcar como Não Lido", + "mark-as-read": "Marcar como Lido", + "deselect-all": "{{common.deselect-all}}" + }, + "card-detail-drawer": { + "general-tab": "Geral", + "metadata-tab": "Metadados", + "cover-tab": "Capa", + "info-tab": "Info", + "no-summary": "Nenhum Resumo disponível.", + "writers-title": "{{series-metadata-detail.writers-title}}", + "genres-title": "{{series-metadata-detail.genres-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "not-defined": "Não definido", + "read": "{{common.read}}", + "unread": "Não Lido", + "files": "Arquivos", + "pages": "Páginas:", + "added": "Adicionado:", + "size": "Tamanho:" + }, + "card-detail-layout": { + "total-items": "{{count}} itens no total" + }, + "card-item": { + "cannot-read": "Não foi Possível Ler" + }, + "chapter-metadata-detail": { + "no-data": "Nenhum metadado disponível", + "writers-title": "{{series-metadata-detail.writers-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "translators-title": "{{series-metadata-detail.translators-title}}", + "letterers-title": "{{series-metadata-detail.letterers-title}}", + "colorists-title": "{{series-metadata-detail.colorists-title}}", + "inkers-title": "{{series-metadata-detail.inkers-title}}", + "pencillers-title": "{{series-metadata-detail.pencillers-title}}", + "cover-artists-title": "{{series-metadata-detail.cover-artists-title}}", + "editors-title": "{{series-metadata-detail.editors-title}}" + }, + "cover-image-chooser": { + "drag-n-drop": "Arrastar e soltar", + "upload": "Upload", + "upload-continued": "uma imagem", + "url-label": "Url", + "load": "Carregar", + "back": "Voltar", + "reset-cover-tooltip": "Redefinir Imagem da Capa", + "reset": "{{common.reset}}", + "image-num": "Imagem {{num}}", + "apply": "{{common.apply}}", + "applied": "{{theme-manager.applied}}" + }, + "download-indicator": { + "progress": "{{percentage}}% baixado" + }, + "edit-series-relation": { + "description-part-1": "Não tem certeza de qual relacionamento adicionar? Veja nosso", + "description-part-2": "wiki para dicas.", + "target-series": "Série de Destino", + "relationship": "Relação", + "remove": "Remover", + "add-relationship": "Adicionar Relação", + "parent": "{{relationship-pipe.parent}}" + }, + "entity-info-cards": { + "tags-title": "{{series-metadata-detail.tags-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "release-date-title": "Lançamento", + "release-date-tooltip": "Data de Lançamento", + "age-rating-title": "Classificação Etária", + "length-title": "Comprimento", + "pages-count": "{{num}} Páginas", + "words-count": "{{num}} Palavras", + "reading-time-title": "Tempo de Leitura", + "date-added-title": "Data de Adição", + "size-title": "Tamanho", + "id-title": "ID", + "links-title": "{{series-metadata-detail.links-title}}", + "isbn-title": "ISBN", + "last-read-title": "Última Leitura", + "less-than-hour": "<1 Hora", + "range-hours": "{{value}} {{hourWord}}", + "hour": "Hora", + "hours": "Horas", + "read-time-title": "{{series-info-cards.read-time-title}}" + }, + "series-info-cards": { + "release-date-title": "{{entity-info-cards.release-date-title}}", + "release-year-tooltip": "Ano de Lançamento", + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "language-title": "Idioma", + "publication-status-title": "Publicação", + "publication-status-tooltip": "Situação da Publicação", + "scrobbling-title": "Scrobbling", + "scrobbling-tooltip": "Situação do Scrobbling", + "on": "Ligado", + "off": "Desligado", + "disabled": "Desabilitado", + "format-title": "Formato", + "last-read-title": "Última Leitura", + "length-title": "Comprimento", + "read-time-title": "Tempo de Leitura", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "time-left-title": "Tempo Restante", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}" + }, + "bulk-add-to-collection": { + "title": "Adicionar à Coleção", + "promoted": "{{common.promoted}}", + "close": "{{common.close}}", + "filter-label": "Filtro", + "clear": "{{common.clear}}", + "no-data": "Nenhuma coleção criada ainda", + "loading": "{{common.loading}}", + "collection-label": "Coleção", + "create": "{{common.create}}" + }, + "entity-title": { + "special": "Especial", + "issue-num": "Número #", + "chapter": "Capítulo" + }, + "external-series-card": { + "open-external": "Abrir Externamente" + }, + "list-item": { + "read": "{{common.read}}" + }, + "manage-alerts": { + "description-part-1": "Esta tabela contém problemas encontrados durante a verificação ou leitura de sua mídia. Esta lista não é gerenciada. Você pode limpá-lo a qualquer momento e usar o Escanear (À Força) Biblioteca para realizar a análise. Uma lista de alguns erros comuns e o que eles significam pode ser encontrada no ", + "description-part-2": "wiki.", + "filter-label": "Filtro", + "clear-alerts": "Limpar Alertas", + "extension-header": "Extensão", + "file-header": "Arquivo", + "comment-header": "Comentário", + "details-header": "Detalhes" + }, + "manage-email-settings": { + "title": "Serviço de E-Mail (SMTP)", + "description": "Kavita vem de fábrica com um serviço de e-mail para executar tarefas como convidar usuários, redefinir senhas, etc. Os e-mails enviados por meio de nosso serviço são excluídos imediatamente. Você pode usar seu próprio serviço de e-mail configurando o serviço {{link}} . Defina a URL do serviço de e-mail e use o botão Testar para garantir que funcione. Você pode redefinir essas configurações para o padrão a qualquer momento. Não há como desabilitar e-mails para autenticação, embora você não seja obrigado a usar um endereço de e-mail válido para os usuários. Os links de confirmação sempre serão salvos nos logs e apresentados na interface do usuário. Os e-mails de registro/confirmação não serão enviados se você não estiver acessando o Kavita por meio de um URL publicamente acessível ou a menos que o recurso Nome do host esteja configurado.", + "send-to-warning": "se você deseja que o Enviar para Dispositivo funcione, você deve hospedar seu próprio serviço de e-mail.", + "email-url-label": "URL do Serviço de E-Mail", + "email-url-tooltip": "Use URL totalmente qualificado do serviço de e-mail. Não inclua a barra final.", + "reset": "{{common.reset}}", + "test": "Teste", + "host-name-label": "Nome do Host", + "host-name-tooltip": "Nome de Domínio (de Proxy Reverso). Se definido, a geração de email sempre usará isso.", + "host-name-validation": "O nome do host deve começar com http(s) e não terminar com /", + "reset-to-default": "{{common.reset-to-default}}", + "save": "{{common.save}}" + }, + "manage-library": { + "title": "Bibliotecas", + "add-library": "Adicionar Biblioteca", + "no-data": "Não há bibliotecas. Tente criar uma.", + "loading": "{{common.loading}}", + "last-scanned-title": "Último Escaneamento:", + "shared-folders-title": "Pastas Compartilhadas:", + "type-title": "Tipo:", + "scan-library": "Escanear Biblioteca", + "delete-library": "Excluir Biblioteca", + "delete-library-by-name": "Excluir {{name}}", + "edit-library": "Editar", + "edit-library-by-name": "Excluir {{name}}" + }, + "manage-media-settings": { + "encode-as-description-part-1": "WebP/AVIF pode reduzir drasticamente os requisitos de espaço para arquivos. WebP/AVIF não é compatível com todos os navegadores ou versões. Para saber se essas configurações são apropriadas para sua configuração, visite ", + "encode-as-description-part-2": "Posso usar o WebP?", + "encode-as-description-part-3": "Posso usar AVIF?", + "encode-as-warning": "Você não pode converter de volta para PNG depois de ir para WebP/AVIF. Você precisaria atualizar as capas em suas bibliotecas para regenerar todas as capas. Marcadores e favicons não podem ser convertidos.", + "media-warning": "Você deve acionar a tarefa de conversão de mídia na aba Tarefas.,", + "encode-as-label": "Salvar Mídia Como", + "encode-as-tooltip": "Todas as mídias que o Kavita gerencia (capas, favoritos, favicons) serão codificadas como este tipo.", + "bookmark-dir-label": "Diretório dos Marcadores", + "bookmark-dir-tooltip": "Local onde os marcadores serão armazenados. Marcadores são arquivos de origem e podem ser grandes. Escolha um local com armazenamento adequado. O diretório é gerenciado; outros arquivos dentro do diretório serão excluídos. Se for Docker, monte um volume adicional e use-o.", + "change": "Mudar", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "media-issue-title": "Problemas na Mídia", + "scrobble-issue-title": "Problemas no Scrobble", + "cover-image-size-label": "Tamanho da Imagem da Capa", + "cover-image-size-tooltip": "Quão grande deve ser as imagens de capas geradas como. Nota: Qualquer coisa maior do que o padrão resultará em tempos de carregamento de página mais longos." + }, + "manage-scrobble-errors": { + "description": "Esta tabela contém problemas encontrados durante o scrobbling. Esta lista não é gerenciada. Você pode limpá-lo a qualquer momento e esperar o próximo upload do scrobble para ver. Se houver uma série desconhecida, é melhor corrigir o nome da série ou o nome da série localizada, ou adicionar um link para os provedores.", + "filter-label": "Filtro", + "clear-errors": "Limpar Erros", + "series-header": "Séries", + "created-header": "Criado", + "comment-header": "Comentário", + "edit-header": "Editar", + "edit-item-alt": "Editar {{seriesName}}" + }, + "default-date-pipe": { + "never": "Nunca" + }, + "manage-settings": { + "notice": "Aviso:", + "restart-required": "Alterar porta, URL base, tamanho do cache ou IPs requer uma reinicialização manual do Kavita para entrar em vigor.", + "base-url-label": "Url Base", + "base-url-tooltip": "Use isso se quiser hospedar o Kavita em um URL base, ou seja, seudominio.com/kavita. Não suportado no Docker usando usuário não root.", + "ip-address-label": "Endereço de IP", + "ip-address-tooltip": "Lista separada por vírgulas de endereços IP que o servidor escuta. Isso é corrigido se você estiver executando no Docker. Requer reinicialização para entrar em vigor.", + "port-label": "Porta", + "port-tooltip": "Porta na qual o servidor escuta. Isso é corrigido se você estiver executando no Docker. Requer reinicialização para entrar em vigor.", + "backup-label": "Dias de Backups", + "backup-tooltip": "O número de backups a serem mantidos. O padrão é 30, o mínimo é 1 e o máximo é 30.", + "log-label": "Dias de Registros", + "log-tooltip": "O número de registros a serem mantidos. O padrão é 30, o mínimo é 1 e o máximo é 30.", + "logging-level-label": "Nível de Registro", + "logging-level-tooltip": "Use a depuração para ajudar a identificar problemas. A depuração pode consumir muito espaço em disco.", + "cache-size-label": "Tamanho do Cache", + "cache-size-tooltip": "A quantidade de memória permitida para cache de APIs pesadas. O padrão é 75 MB.", + "on-deck-last-progress-label": "Última Leitura Na Estante (dias)", + "on-deck-last-progress-tooltip": "O número de dias desde o último progresso antes de começar algo Na Estante.", + "on-deck-last-chapter-add-label": "Último Capítulo Adicionado Na Estante (dias)", + "on-deck-last-chapter-add-tooltip": "O número de dias desde o último capítulo foi adicionado para incluir algo Na Estante.", + "allow-stats-label": "Permitir coleta de uso anônimo", + "allow-stats-tooltip-part-1": "Envie dados de uso anônimos para os servidores da Kavita. Isso inclui informações sobre determinados recursos usados, número de arquivos, versão do sistema operacional, versão de instalação do Kavita, CPU e memória. Usaremos essas informações para priorizar recursos, correções de bugs e ajuste de desempenho. Requer reinicialização para entrar em vigor. Veja o ", + "allow-stats-tooltip-part-2": "para saber o que é coletado.", + "send-data": "Enviar Dados", + "opds-label": "OPDS", + "opds-tooltip": "O suporte ao OPDS permitirá que todos os usuários usem o OPDS para ler e baixar o conteúdo do servidor.", + "enable-opds": "Habilitar OPDS", + "folder-watching-label": "Monitoramento de Pastas", + "folder-watching-tooltip": "Permite que o Kavita monitore as pastas da biblioteca para detectar alterações e invocar a verificação dessas alterações. Isso permite que o conteúdo seja atualizado sem invocar varreduras manualmente ou esperar por varreduras noturnas.", + "enable-folder-watching": "Habilitar Monitoramento de Pastas", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "Você deve ter pelo menos 50 MB.", + "field-required": "{{validation.field-required}}", + "max-logs-validation": "Você não pode ter mais de {{num}} registros", + "min-logs-validation": "Você deve ter pelo menos 1 registro", + "min-days-validation": "Deve ser pelo menos 1 dia", + "min-cache-validation": "Deve ser de 50 MB.", + "max-backup-validation": "Você não pode ter mais de {{num}} backups", + "min-backup-validation": "Você deve ter pelo menos 1 backup", + "ip-address-validation": "Endereços IP só podem conter endereços IPv4 ou IPv6 válidos", + "base-url-validation": "O URL base deve começar e terminar com /" + }, + "manage-system": { + "title": "Sobre o Sistema", + "version-title": "Versão", + "installId-title": "ID da Instalação", + "more-info-title": "Mais Info", + "home-page-title": "Página inicial:", + "wiki-title": "Wiki:", + "discord-title": "Discord:", + "donations-title": "Doações:", + "source-title": "Fonte:", + "feature-request-title": "Pedidos de Recursos" + }, + "manage-tasks-settings": { + "title": "Tarefas Recorrentes", + "library-scan-label": "Escanear Biblioteca", + "library-scan-tooltip": "Com que frequência o Kavita verificará e atualizará os metadados nos arquivos da biblioteca.", + "library-database-backup-label": "Backup do Banco de Dados da Biblioteca", + "library-database-backup-tooltip": "Com que frequência o Kavita fará backup do banco de dados.", + "adhoc-tasks-title": "Tarefas Ad-hocs", + "job-title-header": "Título da Tarefa", + "description-header": "Descrição", + "action-header": "Ação", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}", + "last-executed-header": "Última Execução", + "cron-header": "", + "convert-media-task": "Converter Mídia para Codificação de Destino", + "convert-media-task-desc": "Executa uma tarefa de longa duração que converterá todas as mídias gerenciadas pelo kavita na codificação de destino. Isso é lento (especialmente em dispositivos ARM).", + "convert-media-success": "A Conversão de Mídia para Codificação de Destino foi enfileirada", + "bust-cache-task": "", + "bust-cache-task-desc": "", + "bust-cache-task-success": "", + "clear-reading-cache-task": "Limpar Cache de Leitura", + "clear-reading-cache-task-desc": "Limpa os arquivos em cache para leitura. Útil quando você acabou de atualizar um arquivo que estava lendo nas últimas 24 horas.", + "clear-reading-cache-task-success": "O cache foi limpo", + "clean-up-want-to-read-task": "Limpar Quero Ler", + "clean-up-want-to-read-task-desc": "Remove todas as séries que os usuários leram completamente que estão dentro de Quero Ler e têm um status de publicação Concluído. Funciona a cada 24 horas.", + "clean-up-want-to-read-task-success": "Quero Ler foi limpo", + "backup-database-task": "Backup do Banco de Dados", + "backup-database-task-desc": "Faz backup do banco de dados, marcadores, temas, capas carregadas manualmente e arquivos de configuração.", + "backup-database-task-success": "Uma tarefa para fazer backup do banco de dados foi enfileirada", + "download-logs-task": "Registros de Download", + "download-logs-task-desc": "Compila todos os arquivos de registro em um zip e baixa ele.", + "analyze-files-task": "Analisar Arquivos", + "analyze-files-task-desc": "Executa uma tarefa de longa duração que analisará arquivos para gerar extensão e tamanho. Isso deve ser executado apenas uma vez para a versão v0.7. Não é necessário se você instalou o pós v0.7.", + "analyze-files-task-success": "A análise do arquivo foi colocada na fila", + "check-for-updates-task": "Verifique se há atualizações", + "check-for-updates-task-desc": "Veja se há algum lançamento Estável antes da sua versão." + }, + "manage-users": { + "title": "Usuários Ativos", + "invite": "Convite", + "you-alt": "(Você)", + "pending-title": "Pedente", + "delete-user-tooltip": "Excluir Usuário", + "delete-user-alt": "Excluir Usuário {{user}}", + "edit-user-tooltip": "Editar", + "edit-user-alt": "Editar Usuário {{user}}", + "resend-invite-tooltip": "Reenviar Convite", + "resend-invite-alt": "Reenviar Convite {{user}}", + "setup-user-tooltip": "Configurar Usuário", + "setup-user-alt": "Configurar Usuário {{user}}", + "change-password-tooltip": "Alterar Senha", + "change-password-alt": "Alterar Senha {{user}}", + "resend": "Reenviar", + "setup": "Configurar", + "last-active-title": "Última Atividade:", + "roles-title": "Papéis:", + "none": "Nada", + "never": "Nunca", + "online-now-tooltip": "Online Agora", + "sharing-title": "Compartilhando:", + "no-data": "Não há outros usuários.", + "loading": "{{common.loading}}" + }, + "edit-collection-tags": { + "title": "Editar {{collectionName}} Coleção", + "required-field": "{{validation.required-field}}", + "save": "{{common.save}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "general-tab": "Geral", + "cover-image-tab": "Imagem da Capa", + "series-tab": "Séries", + "name-label": "Nome", + "name-validation": "Nome deve ser único", + "promote-label": "Promoção", + "promote-tooltip": "Promoção significa que a tag pode ser vista em todo o servidor, não apenas para usuários administrativos. Todas as séries que possuem essa tag ainda terão restrições de acesso do usuário.", + "summary-label": "Sumário", + "series-title": "Aplicar a Séries", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}" + }, + "library-detail": { + "library-tab": "Biblioteca", + "recommended-tab": "Recomendado" + }, + "library-recommended": { + "no-data": "Nada para mostrar aqui. Adicione alguns metadados à sua biblioteca, leia algo ou avalie algo. Esta biblioteca também pode ter recomendações desativadas.", + "more-in-genre": "Mais em {{genre}}", + "rediscover": "Redescobrir", + "highly-rated": "Bem Avaliado", + "quick-catchups": "Atualizações Rápidas", + "quick-reads": "Leituras Rápidas", + "on-deck": "{{dashboard.on-deck-title}}" + }, + "admin-dashboard": { + "title": "Painel do Administrador", + "general-tab": "Geral", + "users-tab": "Usuários", + "libraries-tab": "Bibliotecas", + "media-tab": "Mídia", + "logs-tab": "Registros", + "email-tab": "Email", + "tasks-tab": "Tarefas", + "statistics-tab": "Estatísticas", + "system-tab": "Sistema", + "kavita+-tab": "Kavita+", + "kavita+-desc-part-1": "Kavita+ é um serviço de assinatura premium que desbloqueia recursos para todos os usuários nesta instância Kavita. Compre uma assinatura para desbloquear ", + "kavita+-desc-part-2": "benefícios premium", + "kavita+-desc-part-3": "hoje!" + }, + "collection-detail": { + "no-data": "Não há itens. Tente adicionar uma série.", + "no-data-filtered": "Nenhum item corresponde ao seu filtro atual.", + "title-alt": "Kavita - {{collectionName}} Coleção" + }, + "all-collections": { + "title": "Coleções", + "item-count": "{{common.item-count}}", + "no-data": "Não há coleções.", + "create-one-part-1": "Tente criar", + "create-one-part-2": "um" + }, + "carousel-reel": { + "prev-items": "Itens anteriores", + "next-items": "Itens Seguintes" + }, + "draggable-ordered-list": { + "instructions-alt": "Ao colocar um número na entrada de reordenar, o item será inserido naquele local e todos os demais itens terão sua ordem atualizada.", + "reorder-label": "Reordenar", + "remove-item-alt": "Remover item" + }, + "reading-lists": { + "title": "Listas de Leitura", + "item-count": "{{common.item-count}}", + "no-data": "Não há listas de leitura.", + "create-one-part-1": "Tente criar", + "create-one-part-2": "uma" + }, + "reading-list-item": { + "remove": "{{common.remove}}", + "read": "{{common.read}}" + }, + "reading-list-detail": { + "item-count": "{{common.item-count}}", + "page-settings-title": "Configurações da Página", + "remove-read": "Remover Leitura", + "order-numbers-label": "Ordenar Números", + "continue": "Continuar", + "read": "{{common.read}}", + "read-options-alt": "Opções de leitura", + "incognito-alt": "(Incógnito)", + "no-data": "Nada adicionado", + "characters-title": "{{series-metadata-detail.characters-title}}" + }, + "events-widget": { + "title-alt": "Atividade", + "dismiss-all": "Dispensar Todos", + "update-available": "Atualização disponível", + "downloading-item": "Baixando {{item}}", + "more-info": "Clique para mais informações", + "close": "{{common.close}}", + "users-online-count": "{{num}} Usuários online", + "active-events-title": "Eventos Ativos:", + "no-data": "Não há muita coisa acontecendo aqui" + }, + "shortcuts-modal": { + "title": "Atalhos de Teclado", + "close": "{{common.close}}", + "prev-page": "Mover para a página anterior", + "next-page": "Mover para a próxima página", + "go-to": "Abrir caixa de diálogo Ir para Página", + "bookmark": "Marcar página atual", + "double-click": "clique-duplo", + "close-reader": "Fechar leitor", + "toggle-menu": "Alternar Menu" + }, + "grouped-typeahead": { + "files": "Arquivos", + "chapters": "Capítulos", + "people": "Pessoas", + "tags": "Tags", + "genres": "Gêneros", + "libraries": "Bibliotecas", + "reading-lists": "Listas de Leitura", + "collections": "Coleções", + "close": "{{common.close}}", + "loading": "{{common.loading}}" + }, + "nav-header": { + "skip-alt": "Ir para o conteúdo principal", + "search-series-alt": "Pesquisar séries", + "search-alt": "Pesquisar…", + "promoted": "(promovido)", + "no-data": "Nenhum resultado encontrado", + "scroll-to-top-alt": "Rolar para o Topo", + "server-settings": "Configurações do Servidor", + "settings": "Configurações", + "help": "Ajuda", + "announcements": "Anúncios", + "logout": "Sair" + }, + "add-to-list-modal": { + "title": "Adicionar à Lista de Leitura", + "close": "{{common.close}}", + "filter-label": "Filtro", + "promoted-alt": "Promovido", + "no-data": "Nenhuma lista criada ainda", + "loading": "{{common.loading}}", + "reading-list-label": "Lista de Leitura", + "create": "{{common.create}}" + }, + "edit-reading-list-modal": { + "title": "Editar lista de leitura: {{name}}", + "general-tab": "Geral", + "cover-image-tab": "Imagem da Capa", + "close": "{{common.close}}", + "save": "{common.save}}", + "year-validation": "Deve ser maior que 1000, 0 ou em branco", + "month-validation": "Deve estar entre 1 e 12 ou em branco", + "name-unique-validation": "O nome deve ser único", + "required-field": "{{validation.required-field}}", + "summary-label": "Sumário", + "year-label": "Ano", + "month-label": "Mês", + "ending-title": "Terminando", + "starting-title": "Começando", + "promote-label": "Promover", + "promote-tooltip": "Promoção significa que a tag pode ser vista em todo o servidor, não apenas para usuários administrativos. Todas as séries que possuem essa tag ainda terão restrições de acesso do usuário." + }, + "import-cbl-modal": { + "close": "{{common.close}}", + "title": "Importar CBL", + "import-description": "Para começar, importe um arquivo .cbl. O Kavita executará várias verificações antes de importar. Algumas etapas bloquearão o avanço devido a problemas com o arquivo.", + "validate-description": "Todos os arquivos foram validados para ver se há alguma operação a ser feita na lista. Todas as listas que falharam não passarão para a próxima etapa. Corrija os arquivos CBL e tente novamente.", + "validate-warning": "Existem problemas com o CBL que impedirão uma importação. Corrija esses problemas e tente novamente.", + "validate-no-issue": "Parece bom", + "validate-no-issue-description": "Nenhum problema encontrado com CBL, pressione próximo.", + "dry-run-description": "Esta é uma simulação e mostra o que acontecerá se você pressionar Avançar e executar a importação. Todas as falhas não serão importadas.", + "prev": "Anterior", + "import": "Importar", + "restart": "Reiniciar", + "next": "Seguinte", + "import-step": "Importar CBLs", + "validate-cbl-step": "Validar CBL", + "dry-run-step": "", + "final-import-step": "Passo Final" + }, + "pdf-reader": { + "loading-message": "Carregando... PDFs podem demorar mais do que o esperado", + "incognito-mode": "Modo Incógnito", + "light-theme-alt": "Tema Claro", + "dark-theme-alt": "Tema Escuro", + "close-reader-alt": "Fechar Leitor" + }, + "manga-reader": { + "back": "Voltar", + "save-globally": "Salvar Globalmente", + "incognito-alt": "O modo de navegação anônima está ativado. Alterne para desligar.", + "incognito-title": "Modo Incógnito:", + "shortcuts-menu-alt": "Modal de Atalhos de Teclado", + "prev-page-tooltip": "Página Anterior", + "next-page-tooltip": "Página Seguinte", + "prev-chapter-tooltip": "Capítulo/Volume Anterior", + "next-chapter-tooltip": "Capítulo/Volume Seguinte", + "first-page-tooltip": "Primeira Página", + "last-page-tooltip": "Última Página", + "left-to-right-alt": "Esquerda para Direita", + "right-to-left-alt": "Direita para Esquerda", + "reading-direction-tooltip": "Direção da Leitura: ", + "reading-mode-tooltip": "Modo de Leitura", + "collapse": "Agrupar", + "fullscreen": "Tela Cheia", + "settings-tooltip": "Configurações", + "image-splitting-label": "Divisão de Imagem", + "image-scaling-label": "Escala da Imagem", + "height": "Altura", + "width": "Largura", + "original": "Original", + "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "swipe-enabled-label": "Deslizar Ativado", + "enable-comic-book-label": "Emular quadrinhos", + "brightness-label": "Brilho", + "first-time-reading-manga": "Toque na imagem a qualquer momento para abrir o menu. Você pode definir configurações diferentes ou ir para a página clicando na barra de progresso. Toque nos lados da imagem para mover para a página seguinte/anterior.", + "layout-mode-switched": "Modo de layout alterado para Único devido a espaço insuficiente para renderizar layout duplo", + "no-next-chapter": "Nenhum Capítulo A Seguir", + "no-prev-chapter": "Nenhum Capítulo Anterior", + "user-preferences-updated": "Preferências de usuário atualizadas", + "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}" + }, + "metadata-filter": { + "filter-title": "Filtro", + "format-label": "Formato", + "libraries-label": "Bibliotecas", + "collections-label": "Coleções", + "genres-label": "Gêneros", + "tags-label": "Tags", + "cover-artist-label": "Artista da Capa", + "writer-label": "Escritor", + "publisher-label": "Editora", + "penciller-label": "Desenhista", + "letterer-label": "Letrista", + "inker-label": "Arte-finalista", + "editor-label": "Editor", + "colorist-label": "Colorista", + "character-label": "Personagem", + "translator-label": "Tradutor", + "read-progress-label": "Progresso da Leitura", + "unread": "Não Lido", + "read": "Lido", + "in-progress": "Em Progresso", + "rating-label": "Avaliação", + "age-rating-label": "Classificação Etária", + "language-label": "Idioma", + "publication-status-label": "Situação da Publicação", + "series-name-label": "Nome das Séries", + "series-name-tooltip": "O nome da série será filtrado por Nome, Nome de classificação ou Nome localizado", + "release-label": "Lançamento", + "min": "", + "max": "", + "sort-by-label": "Ordernar Por", + "ascending-alt": "Ascendente", + "descending-alt": "Descendente", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "limit-label": "Limitar A" + }, + "sort-field-pipe": { + "sort-name": "Nome Classificável", + "created": "Criado", + "last-modified": "Última Modificação", + "last-chapter-added": "Item Adicionado", + "time-to-read": "Tempo para Ler", + "release-year": "Ano de Lançamento" + }, + "edit-series-modal": { + "title": "{{seriesName}} Detalhes", + "general-tab": "Geral", + "metadata-tab": "Metadados", + "people-tab": "Pessoas", + "web-links-tab": "Links da Web", + "cover-image-tab": "Imagem da Capa", + "related-tab": "Relacionado", + "info-tab": "Info", + "collections-label": "Coleções", + "genres-label": "Gêneros", + "tags-label": "Tags", + "cover-artist-label": "Artista da Capa", + "writer-label": "Escritor", + "publisher-label": "Editora", + "penciller-label": "Desenhista", + "letterer-label": "Letrista", + "inker-label": "Arte-finalista", + "editor-label": "Editor", + "colorist-label": "Colorista", + "character-label": "Personagem", + "translator-label": "Tradutor", + "language-label": "Idioma", + "age-rating-label": "Classificação Etária", + "publication-status-label": "Situação da Publicação", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "name-label": "Nome", + "sort-name-label": "Nome Classificável", + "localized-name-label": "Nome Localizado", + "summary-label": "Sumário", + "release-year-label": "Ano de Lançamento", + "web-link-description": "Aqui você pode adicionar muitos links diferentes para serviços externos.", + "web-link-label": "Link da Web", + "add-link-alt": "Adicionar Link", + "remove-link-alt": "Remover Link", + "cover-image-description": "Carregue e escolha uma nova imagem de capa. Pressione Salvar para carregar e substituir a capa.", + "save": "{{common.save}}", + "field-locked-alt": "Campo está bloqueado", + "info-title": "informação", + "library-title": "Biblioteca:", + "format-title": "Formato:", + "created-title": "Criado:", + "last-read-title": "Última Leitura:", + "last-added-title": "Último Item Adicionado:", + "last-scanned-title": "Último Escaneamento:", + "folder-path-title": "Caminho da Pasta:", + "publication-status-title": "Situação da Publicação:", + "total-pages-title": "Total de Páginas:", + "total-items-title": "Total de Itens:", + "max-items-title": "Máx. de Itens:", + "size-title": "Tamanho:", + "loading": "{{common.loading}}", + "added-title": "Adicionado:", + "last-modified-title": "Última Modificação:", + "view-files": "Exibir Arquivos", + "pages-title": "Páginas:", + "chapter-title": "Capítulo:", + "volume-num": "{{common.volume-num}}", + "highest-count-tooltip": "Número mais alto encontrado em todos os ComicInfo da Série", + "max-issue-tooltip": "Campo Máx. de Número ou Volume de todos ComicInfo nas Séries" + }, + "day-breakdown": { + "title": "", + "x-axis-label": "Dia da Semana", + "y-axis-label": "Eventos de Leitura" + }, + "file-breakdown-stats": { + "format-title": "Formato", + "format-tooltip": "Não Classificado significa que o Kavita não verificou alguns arquivos. Isso ocorre em arquivos antigos existentes antes da v0.7. Pode ser necessário executar uma verificação forçada por meio do modal de configurações da Biblioteca.", + "visualisation-label": "Visualização", + "data-table-label": "Tabela de Dados", + "extension-header": "Extensão", + "format-header": "Formato", + "total-size-header": "Tamanho Total", + "total-files-header": "Total de Arquivos", + "not-classified": "Não Classificado", + "total-file-size-title": "Tamanho Total do Arquivo:" + }, + "reading-activity": { + "title": "Atividade de Leitura", + "legend-label": "Formatos", + "x-axis-label": "Tempo", + "y-axis-label": "Horas de Leitura", + "no-data": "Nenhum Progresso na Leitura", + "time-frame-label": "Prazo", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" + }, + "manga-format-stats": { + "title": "Formato", + "visualisation-label": "Visualização", + "data-table-label": "Tabela de Dados", + "format-header": "Formato", + "count-header": "Número" + }, + "publication-status-stats": { + "title": "Situação da Publicação", + "visualisation-label": "Visualização", + "data-table-label": "Tabela de Dados", + "year-header": "Ano", + "count-header": "Número" + }, + "server-stats": { + "total-series-label": "Total de Séries", + "total-series-tooltip": "Total de Séries: {{count}}", + "total-volumes-label": "Total de Volumes", + "total-volumes-tooltip": "Total de Volumes: {{count}}", + "total-files-label": "Total de Arquivos", + "total-files-tooltip": "Total de Arquivos: {{count}}", + "total-size-label": "Tamanho Total", + "total-genres-label": "Total de Gêneros", + "total-genres-tooltip": "Total de Gêneros: {{count}}", + "total-tags-label": "Total de Tags", + "total-tags-tooltip": "Total de Tags: {{count}}", + "total-people-label": "Total de Pessoas", + "total-people-tooltip": "Total de Pessoas: {{count}}", + "total-read-time-label": "Tempo Total de Leitura", + "total-read-time-tooltip": "Tempo Total de Leitura: {{count}}", + "series": "séries", + "reads": "leituras", + "release-years-title": "Anos de Lançamento", + "most-active-users-title": "Usuários Mais Ativos", + "popular-libraries-title": "Bibliotecas Populares", + "popular-series-title": "Séries Populares", + "recently-read-title": "Lido Recentemente", + "genre-count": "{{num}} Gêneros", + "tag-count": "{{num}} Tags", + "people-count": "{{num}} Pessoas", + "tags": "Tags", + "people": "Pessoas", + "genres": "Gêneros" + }, + "errors": { + "series-doesnt-exist": "Esta série não existe mais", + "collection-invalid-access": "Você não tem acesso a nenhuma biblioteca a que esta tag pertence ou esta coleção é inválida", + "unknown-crit": "Ocorreu um erro crítico desconhecido", + "user-not-auth": "O usuário não está autenticado", + "error-code": "{{num}} Erro", + "download": "Ocorreu um problema ao baixar este arquivo ou você não tem permissões", + "not-found": "Essa url não existe", + "generic": "Algo inesperado deu errado", + "rejected-cover-upload": "A imagem não pôde ser buscada porque o servidor recusou a solicitação. Em vez disso, faça o download e o upload do arquivo.", + "invalid-confirmation-url": "URL de confirmação inválida", + "invalid-confirmation-email": "E-mail de confirmação inválido", + "invalid-password-reset-url": "URL de redefinição de senha inválida" + }, + "toasts": { + "regen-cover": "Uma tarefa foi colocada na fila para gerar novamente a imagem da capa", + "no-pages": "Não há páginas. Kavita não conseguiu ler este arquivo.", + "download-in-progress": "O download já está em andamento. Por favor, aguarde.", + "scan-queued": "Escaneamento na fila para {{name}}", + "server-settings-updated": "Configurações do servidor atualizadas", + "reset-ip-address": "Endereços IP Redefinidos", + "reset-base-url": "Redefinir Url Base", + "unauthorized-1": "Você não está autorizado a visualizar esta página.", + "unauthorized-2": "Desautorizado", + "no-updates": "Nenhuma atualização disponível", + "confirm-delete-user": "Tem certeza de que deseja excluir este usuário?", + "user-deleted": "{{user}} foi excluído", + "email-sent-to-user": "E-mail enviado para {{user}}", + "click-email-link": "Clique neste link para confirmar seu e-mail. Você deve confirmar para poder fazer o login.", + "series-added-to-collection": "Série adicionada à coleção {{collectionName}}", + "no-series-collection-warning": "Aviso! Nenhuma série foi selecionada. Salvar excluirá a Coleção. Você tem certeza que quer continuar?", + "collection-updated": "Coleção atualizada", + "reading-list-deleted": "Lista de leitura excluída", + "reading-list-updated": "Lista de leitura atualizada", + "confirm-delete-reading-list": "Tem certeza de que deseja excluir a lista de leitura? Isto não pode ser desfeito.", + "item-removed": "Item removido", + "nothing-to-remove": "Nada a remover", + "series-added-to-reading-list": "Série adicionada à lista de leitura", + "volumes-added-to-reading-list": "Volume adicionado à lista de leitura", + "chapter-added-to-reading-list": "Capítulo adicionado à lista de leitura", + "multiple-added-to-reading-list": "Capítulos e volumes adicionados à lista de leitura", + "select-files-warning": "Você precisa selecionar arquivos para avançar", + "reading-list-imported": "Lista de leitura importada", + "incognito-off": "O modo de navegação anônima está desativado. O progresso agora começará a ser rastreado.", + "email-service-reset": "Redefinição do Serviço de E-mail", + "email-service-reachable": "O serviço de e-mail estava acessível", + "email-service-unresponsive": "O URL do serviço de e-mail não respondeu.", + "refresh-covers-queued": "Atualizar capas na fila para {{name}}", + "library-file-analysis-queued": "Análise de arquivo de biblioteca na fila para {{name}}", + "entity-read": "{{name}} agora é lido", + "entity-unread": "{{name}} agora é não lido", + "mark-read": "Marcar como Lido", + "mark-unread": "Marcar como Não Lido", + "series-removed-want-to-read": "Série removida da lista de Quero Ler", + "series-deleted": "Série excluída", + "file-send-to": "Arquivo(s) enviado(s) para {{name}}", + "theme-missing": "O tema ativo não existe mais. Atualize a página.", + "email-sent": "E-mail enviado para {{email}}", + "k+-license-saved": "Chave de licença salva, mas não é válida. Clique em verificar para revalidar a assinatura. O registro pela primeira vez pode demorar um minuto para se propagar.", + "k+-unlocked": "Kavita+ desbloqueado!", + "k+-error": "Ocorreu um erro ao ativar sua licença. Por favor, tente novamente.", + "k+-delete-key": "Isso apenas excluirá a chave de licença do Kavita e permitirá que um link de compra seja exibido. Isso não cancelará sua assinatura! Use isso somente se instruído pelo suporte!", + "library-deleted": "A biblioteca {{name}} foi removida", + "copied-to-clipboard": "Copiado para a área de transferência", + "book-settings-info": "Você pode modificar as configurações do livro, salvar essas configurações para todos os livros e visualizar o sumário da gaveta.", + "no-next-chapter": "Não foi possível encontrar o próximo {{entity}}", + "no-prev-chapter": "Não foi possível encontrar {{entity}} anterior", + "load-next-chapter": "Próxima {{entity}} carregada", + "load-prev-chapter": "{{entity}} anterior carregada", + "account-registration-complete": "Registro da conta concluído", + "account-migration-complete": "Migração de conta concluída", + "password-reset": "Redefinição de senha", + "password-updated": "A senha foi atualizada", + "forced-scan-queued": "Uma verificação forçada foi iniciada para {{name}}", + "library-created": "Biblioteca criada com sucesso. Uma verificação foi iniciada.", + "anilist-token-updated": "O Token AniList foi atualizado", + "age-restriction-updated": "Classificação etária foi atualizada", + "email-sent-to-no-existing": "Um e-mail foi enviado para {{email}} para confirmação.", + "email-sent-to": "Um e-mail foi enviado para seu endereço de e-mail antigo para confirmação.", + "change-email-private": "O servidor não é acessível publicamente. Peça ao administrador para buscar seu link de confirmação nos registros", + "device-updated": "Dispositivo atualizado", + "device-created": "Dispositivo criado", + "confirm-regen-covers": "Atualizar capas forçará o recalculo de todas as imagens de capa. Esta é uma operação pesada. Tem certeza de que não deseja executar uma verificação?", + "alert-long-running": "Este é um processo de execução longa. Por favor, dê-lhe tempo para ser concluído antes de invocar novamente.", + "confirm-delete-multiple-series": "Tem certeza de que deseja excluir {{count}} séries? Ele não modificará os arquivos no disco.", + "confirm-delete-series": "Tem certeza de que deseja excluir esta série? Ele não modificará os arquivos no disco.", + "alert-bad-theme": "Há um CSS inválido ou inseguro no tema. Entre em contato com seu administrador para corrigir isso. Padrão para tema escuro.", + "confirm-library-delete": "Tem certeza de que deseja excluir a biblioteca {{name}}? Você não pode desfazer esta ação.", + "confirm-library-type-change": "A alteração do tipo de biblioteca acionará uma nova verificação com regras de análise diferentes e pode levar à recriação de séries e, portanto, você pode perder o progresso e os favoritos. Você deve fazer backup antes de fazer isso. Você tem certeza que quer continuar?", + "confirm-download-size": "O {{entityType}} é {{size}}. Você tem certeza que quer continuar?", + "list-doesnt-exist": "Esta lista não existe" + }, + "actionable": { + "scan-library": "Escanear Biblioteca", + "refresh-covers": "Atualizar Capas", + "analyze-files": "Analisar Arquivos", + "settings": "Configurações", + "edit": "Editar", + "mark-as-read": "Marcar como Lido", + "mark-as-unread": "Marcar como Não Lido", + "scan-series": "Escanear Séries", + "add-to": "Adicionar a", + "add-to-want-to-read": "Adicionar ao Quero ler", + "remove-from-want-to-read": "Remover do Quero Ler", + "remove-from-on-deck": "Remover de Na Estante", + "others": "Outros", + "add-to-reading-list": "Adicionar à Lista de Leitura", + "add-to-collection": "Adicionar a Coleção", + "send-to": "Enviar Para", + "delete": "Excluir", + "download": "Baixar", + "read-incognito": "Ler Anônimo", + "details": "Detalhes", + "view-series": "Exibir Séries", + "clear": "Limpar", + "import-cbl": "Importar CBL", + "read": "Ler", + "add-rule-group-or": "Adicionar Grupo de Regras (OU)", + "add-rule-group-and": "Adicionar Grupo de Regras (E)", + "remove-rule-group": "Remover Grupo de Regras" + }, + "preferences": { + "left-to-right": "Esquerda para Direita", + "right-to-left": "Direita para Esquerda", + "horizontal": "Horizontal", + "vertical": "Vertical", + "automatic": "Automático", + "fit-to-height": "Ajustar à Altura", + "fit-to-width": "Ajustar à Largura", + "original": "Original", + "fit-to-screen": "Ajustar a Tela", + "no-split": "Sem Divisão", + "webtoon": "Webtoon", + "single": "Único", + "double": "Duplo", + "double-manga": "Duplo (Mangá)", + "scroll": "Rolar", + "1-column": "1 Coluna", + "2-column": "2 Coluna", + "cards": "Cartões", + "list": "Lista", + "up-to-down": "De Cima para Baixo" + }, + "validation": { + "required-field": "Este campo é obrigatório", + "valid-email": "Este deve ser um e-mail válido", + "password-validation": "A senha deve ter entre 6 e 32 caracteres" + }, + "entity-type": { + "volume": "volume", + "chapter": "capítulo", + "series": "séries", + "bookmark": "marcador", + "logs": "registros" + }, + "common": { + "reset-to-default": "Redefinir para o Padrão", + "close": "Fechar", + "cancel": "Cancelar", + "create": "Criar", + "save": "Salvar", + "reset": "Redefinir", + "add": "Adicionar", + "apply": "Aplicar", + "delete": "Excluir", + "edit": "Editar", + "help": "Ajuda", + "submit": "Enviar", + "email": "Email", + "read": "Ler", + "loading": "Carregando…", + "username": "Nome do usuário", + "password": "Senha", + "promoted": "Promovido", + "select-all": "Selecionar Tudo", + "deselect-all": "Deselecionar Todos", + "series-count": "{{num}} Séries", + "item-count": "{{num}} Itens", + "book-num": "Livro", + "issue-hash-num": "Número #", + "issue-num": "Número", + "chapter-num": "Capítulo", + "volume-num": "Volume" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "Role para cima para ir para o capítulo anterior", + "continuous-reading-prev-chapter": "Capítulo Anterior", + "continuous-reading-next-chapter": "Capítulo Seguinte", + "continuous-reading-next-chapter-alt": "Role para cima para ir para o próximo capítulo" + }, + "metadata-builder": { + "or": "Corresponda a qualquer um dos seguintes", + "and": "Corresponde a todos os seguintes", + "add-rule": "Adicionar Regra", + "remove-rule": "Remover Linha {{num}}" + }, + "filter-field-pipe": { + "age-rating": "Classificação Etária", + "characters": "Personagens", + "collection-tags": "Coleção de Tags", + "colorist": "Colorista", + "cover-artist": "Artista da Capa", + "editor": "Editor", + "genres": "Gêneros", + "inker": "Arte-finalista", + "languages": "Idiomas", + "libraries": "Bibliotecas", + "letterer": "Letrista", + "publication-status": "Situação da Publicação", + "penciller": "Desenhista", + "publisher": "Editora", + "read-progress": "Progresso da Leitura", + "read-time": "Tempo de Leitura", + "series-name": "Nome da Série", + "summary": "Sumário", + "tags": "Tags", + "translators": "Tradutores", + "writers": "Escritores", + "formats": "Formatos", + "release-year": "Ano do Lançamento", + "user-rating": "Avaliação do Usuário" + }, + "filter-comparison-pipe": { + "begins-with": "Começa com", + "equal": "Igual", + "greater-than": "Maior que", + "greater-than-or-equal": "Maior ou igual que", + "less-than": "Menor que", + "less-than-or-equal": "Menor ou igual que", + "matches": "Corresponde", + "does-not-contain": "Não contém", + "not-equal": "Não igual", + "ends-with": "Termina com", + "is-before": "É antes de", + "is-after": "É depois de", + "contains": "Contém", + "is-in-last": "É no final de", + "is-not-in-last": "Não é no final de" + }, + "cover-image-size": { + "default": "Padrão (320x455)", + "medium": "Médio (640x909)", + "xlarge": "Extra Grande (1265x1795)", + "large": "Grande (900x1277)" + } +} diff --git a/UI/Web/src/assets/langs/ru.json b/UI/Web/src/assets/langs/ru.json index ef2a56fe97..16acefa6c1 100644 --- a/UI/Web/src/assets/langs/ru.json +++ b/UI/Web/src/assets/langs/ru.json @@ -1283,12 +1283,6 @@ "dark-theme-alt": "", "close-reader-alt": "" }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" - }, "manga-reader": { "back": "", "save-globally": "", @@ -1326,7 +1320,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", diff --git a/UI/Web/src/assets/langs/th.json b/UI/Web/src/assets/langs/th.json index e6fb093572..fe7d69b9cc 100644 --- a/UI/Web/src/assets/langs/th.json +++ b/UI/Web/src/assets/langs/th.json @@ -148,7 +148,7 @@ }, "user-holds": { "title": "Scrobble Holds", - "description": "นี่คือรายการซีรีส์ที่จัดการโดยผู้ใช้ซึ่งจะไม่ถูกบล็อกไปยังผู้ให้บริการอัปสตรีม คุณสามารถนำชุดข้อมูลออกได้ทุกเมื่อ จากนั้นเหตุการณ์ที่สามารถเขียน Scrobble ได้ครั้งถัดไป (ความคืบหน้าในการอ่าน การให้คะแนน สถานะที่ต้องการอ่าน) จะทำให้เกิดขึ้น" + "description": "นี่คือรายการซีรีส์ที่จัดการโดยผู้ใช้ซึ่งจะไม่ถูกบล็อกไปยังผู้ให้บริการอัปสตรีม คุณสามารถนำชุดข้อมูลออกได้ทุกเมื่อ จากนั้นเหตุการณ์ที่สามารถเขียน scrobble ได้ครั้งถัดไป (ความคืบหน้าในการอ่าน การให้คะแนน สถานะที่ต้องการอ่าน) จะทำให้เกิดขึ้น" }, "theme-manager": { "title": "ตัวจัดการธีม", @@ -243,7 +243,8 @@ "regen-warning": "การสร้างคีย์ API ของคุณใหม่จะทำให้ไคลเอนต์ที่มีอยู่ใช้ไม่ได้", "no-key": "ข้อผิดพลาด - ไม่ได้ตั้งค่าคีย์", "confirm-reset": "การดำเนินการนี้จะทำให้การกำหนดค่า OPDS ใดๆ ที่คุณตั้งค่าไว้เป็นโมฆะ คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", - "key-reset": "รีเซ็ตคีย์ API" + "key-reset": "รีเซ็ตคีย์ API", + "show": "แสดง" }, "scrobbling-providers": { "title": "ผู้ให้บริการ Scrobbling", @@ -382,7 +383,7 @@ "month-ago": "หนึ่งเดือนที่ผ่านมา", "months-ago": "{{value}} เดือนที่แล้ว", "year-ago": "เมื่อปีก่อน", - "years-ago": "{[value}} ปีที่แล้ว", + "years-ago": "{{value}} ปีที่แล้ว", "never": "ไม่เคย" }, "relationship-pipe": { @@ -414,26 +415,26 @@ "cover-artist": "ผู้วาดหน้าปก", "editor": "อิดิเตอร์", "inker": "ผู้ลงหมึก", - "letterer": "", - "penciller": "", - "publisher": "", - "writer": "", - "other": "" + "letterer": "เลทเทอเรอ", + "penciller": "เพนซิลเลอ", + "publisher": "สำนักพิมพ์", + "writer": "ผู้แต่ง", + "other": "อื่นๆ" }, "manga-format-pipe": { - "epub": "", - "archive": "", - "image": "", - "pdf": "", - "unknown": "" + "epub": "EPUB", + "archive": "Archive", + "image": "รูปภาพ", + "pdf": "PDF", + "unknown": "ไม่รู้จัก" }, "library-type-pipe": { - "book": "", - "comic": "", - "manga": "" + "book": "หนังสือ", + "comic": "การ์ตูน", + "manga": "มังงะ" }, "age-rating-pipe": { - "unknown": "", + "unknown": "ไม่รู้จัก", "early-childhood": "", "adults-only": "", "everyone": "", @@ -451,20 +452,20 @@ "r18-plus": "" }, "reset-password": { - "title": "", + "title": "รีเซ็ตรหัสผ่าน", "description": "", - "email-label": "", - "required-field": "", - "valid-email": "", - "submit": "" + "email-label": "{{common.email}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "submit": "{{common.submit}}" }, "reset-password-modal": { - "title": "", - "new-password-label": "", - "error-label": "", - "close": "", - "cancel": "", - "save": "" + "title": "รีเซ็ตรหัสผ่านของ {{username}}", + "new-password-label": "รหัสผ่านใหม่", + "error-label": "ข้อผิดพลาด: ", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "all-series": { "series-count": "" @@ -493,7 +494,7 @@ "invite-url-label": "", "invite": "", "inviting": "", - "cancel": "" + "cancel": "{{common.cancel}}" }, "library-selector": { "title": "", @@ -506,7 +507,7 @@ "manage": "", "invalid-license-tooltip": "", "check": "", - "cancel": "", + "cancel": "{{common.cancel}}", "edit": "", "buy": "", "activate": "", @@ -519,7 +520,7 @@ "activate-license-label": "", "activate-email-label": "", "activate-delete": "", - "activate-save": "" + "activate-save": "{{common.save}}" }, "book-line-overlay": { "copy": "", @@ -527,7 +528,7 @@ "close": "", "required-field": "", "bookmark-label": "", - "save": "" + "save": "{{common.save}}" }, "book-reader": { "title": "", @@ -576,12 +577,12 @@ "success": "" }, "confirm-reset-password": { - "title": "", - "description": "", - "password-label": "", - "required-field": "", - "submit": "", - "password-validation": "" + "title": "รีเซ็ตรหัสผ่าน", + "description": "ระบุรหัสผ่านใหม่", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}" }, "register": { "title": "", @@ -712,10 +713,10 @@ "include-in-search-tooltip": "", "force-scan": "", "force-scan-tooltip": "", - "reset": "", - "cancel": "", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", "next": "", - "save": "", + "save": "{{common.save}}", "required-field": "" }, "reader-settings": { @@ -724,7 +725,7 @@ "font-size-label": "", "line-spacing-label": "", "margin-label": "", - "reset-to-defaults": "", + "reset-to-defaults": "รีเซ็ตกลับเป็นค่าเริ่มต้น", "reader-settings-title": "", "reading-direction-label": "", "right-to-left": "", @@ -818,8 +819,8 @@ "url-label": "", "load": "", "back": "", - "reset-cover-tooltip": "", - "reset": "", + "reset-cover-tooltip": "รีเซ็ตภาพปก", + "reset": "{{common.reset}}", "image-num": "", "apply": "", "applied": "" @@ -919,13 +920,13 @@ "send-to-warning": "", "email-url-label": "", "email-url-tooltip": "", - "reset": "", + "reset": "{{common.reset}}", "test": "", "host-name-label": "", "host-name-tooltip": "", "host-name-validation": "", - "reset-to-default": "", - "save": "" + "reset-to-default": "{{common.reset-to-default}}", + "save": "{{common.save}}" }, "manage-library": { "title": "", @@ -947,14 +948,14 @@ "encode-as-description-part-3": "", "encode-as-warning": "", "media-warning": "", - "encode-as-label": "", + "encode-as-label": "บันทึกสื่อเป็น", "encode-as-tooltip": "", "bookmark-dir-label": "", "bookmark-dir-tooltip": "", "change": "", - "reset-to-default": "", - "reset": "", - "save": "", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", "media-issue-title": "", "scrobble-issue-title": "" }, @@ -1002,9 +1003,9 @@ "folder-watching-label": "", "folder-watching-tooltip": "", "enable-folder-watching": "", - "reset-to-default": "", - "reset": "", - "save": "", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", "cache-size-validation": "", "field-required": "", "max-logs-validation": "", @@ -1038,9 +1039,9 @@ "job-title-header": "", "description-header": "", "action-header": "", - "reset-to-default": "", - "reset": "", - "save": "", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", "recurring-tasks-title": "", "last-executed-header": "", "cron-header": "", @@ -1096,9 +1097,9 @@ "edit-collection-tags": { "title": "", "required-field": "", - "save": "", + "save": "{{common.save}}", "close": "", - "cancel": "", + "cancel": "{{common.cancel}}", "general-tab": "", "cover-image-tab": "", "series-tab": "", @@ -1245,7 +1246,7 @@ "general-tab": "", "cover-image-tab": "", "close": "", - "save": "", + "save": "{common.save}}", "year-validation": "", "month-validation": "", "name-unique-validation": "", @@ -1283,15 +1284,9 @@ "dark-theme-alt": "", "close-reader-alt": "" }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" - }, "manga-reader": { "back": "", - "save-globally": "", + "save-globally": "บันทึก", "incognito-alt": "", "incognito-title": "", "shortcuts-menu-alt": "", @@ -1326,7 +1321,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", @@ -1357,7 +1351,7 @@ "sort-by-label": "", "ascending-alt": "", "descending-alt": "", - "reset": "", + "reset": "{{common.reset}}", "apply": "" }, "sort-field-pipe": { @@ -1405,7 +1399,7 @@ "add-link-alt": "", "remove-link-alt": "", "cover-image-description": "", - "save": "", + "save": "{{common.save}}", "field-locked-alt": "", "info-title": "", "library-title": "", @@ -1511,7 +1505,7 @@ "rejected-cover-upload": "", "invalid-confirmation-url": "", "invalid-confirmation-email": "", - "invalid-password-reset-url": "" + "invalid-password-reset-url": "URL รีเซ็ตรหัสผ่านไม่ถูกต้อง" }, "toasts": { "regen-cover": "", @@ -1519,8 +1513,8 @@ "download-in-progress": "", "scan-queued": "", "server-settings-updated": "", - "reset-ip-address": "", - "reset-base-url": "", + "reset-ip-address": "รีเซ็ต IP Addresses", + "reset-base-url": "รีเซ็ต Base Url", "unauthorized-1": "", "unauthorized-2": "", "no-updates": "", @@ -1543,7 +1537,7 @@ "select-files-warning": "", "reading-list-imported": "", "incognito-off": "", - "email-service-reset": "", + "email-service-reset": "รีเซ็ต Email Service", "email-service-reachable": "", "email-service-unresponsive": "", "refresh-covers-queued": "", @@ -1560,7 +1554,7 @@ "k+-license-saved": "", "k+-unlocked": "", "k+-error": "", - "k+-delete-key": "", + "k+-delete-key": "การดำเนินการนี้จะลบ License ของ Kavita และอนุญาตให้แสดงลิงก์ซื้อ การดำเนินการนี้จะไม่ยกเลิกการสมัครรับข้อมูลของคุณ ใช้ฟังก์ชันนี้เฉพาะเมื่อได้รับคำแนะนำจากฝ่ายสนับสนุนเท่านั้น!", "library-deleted": "", "copied-to-clipboard": "", "book-settings-info": "", @@ -1570,7 +1564,7 @@ "load-prev-chapter": "", "account-registration-complete": "", "account-migration-complete": "", - "password-reset": "", + "password-reset": "รีเซ็ตรหัสผ่าน", "password-updated": "", "forced-scan-queued": "", "library-created": "", @@ -1650,12 +1644,12 @@ "logs": "" }, "common": { - "reset-to-default": "", + "reset-to-default": "รีเซ็ตกลับค่าเริ่มต้น", "close": "", - "cancel": "", + "cancel": "ยกเลิก", "create": "", - "save": "", - "reset": "", + "save": "บันทึก", + "reset": "รีเซ็ต", "add": "", "apply": "", "delete": "", diff --git a/UI/Web/src/assets/langs/tr.json b/UI/Web/src/assets/langs/tr.json index c9ee356860..00c9bf19b3 100644 --- a/UI/Web/src/assets/langs/tr.json +++ b/UI/Web/src/assets/langs/tr.json @@ -32,9 +32,9 @@ "filter-label": "Filtre", "created-header": "Oluşturulmuş", "last-modified-header": "En son değiştirilmiş", - "type-header": "", + "type-header": "Tür", "series-header": "Seriler", - "data-header": "", + "data-header": "Veri", "is-processed-header": "", "no-data": "Bilgi yok", "volume-and-chapter-num": "", @@ -63,7 +63,7 @@ "review-card-modal": { "close": "", "user-review": "{{username}} İncelemesi", - "external-mod": "", + "external-mod": "(harici)", "go-to-review": "İncelemeye git" }, "review-card": { @@ -1283,12 +1283,6 @@ "dark-theme-alt": "", "close-reader-alt": "" }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" - }, "manga-reader": { "back": "", "save-globally": "", @@ -1326,7 +1320,6 @@ "metadata-filter": { "filter-title": "", "format-label": "", - "format-tooltip": "", "libraries-label": "", "collections-label": "", "genres-label": "", diff --git a/UI/Web/src/assets/langs/zh_Hans.json b/UI/Web/src/assets/langs/zh_Hans.json index 0b7cc2eed2..c280d249b7 100644 --- a/UI/Web/src/assets/langs/zh_Hans.json +++ b/UI/Web/src/assets/langs/zh_Hans.json @@ -1,6 +1,6 @@ { "login": { - "title": "登陆", + "title": "登录", "username": "{{common.username}}", "password": "{{common.password}}", "password-validation": "{{validation.password-validation}}", @@ -8,10 +8,10 @@ "submit": "{{common.submit}}" }, "dashboard": { - "no-libraries": "尚未有库信息。先配置一些", + "no-libraries": "还没有设置资料库,请先配置一些", "server-settings-link": "服务器设置", - "not-granted": "你尚未获得任一图书馆的权限。", - "on-deck-title": "待阅", + "not-granted": "您没有权限访问资料库", + "on-deck-title": "最近阅读", "recently-updated-title": "最近更新的系列", "recently-added-title": "新增系列" }, @@ -27,11 +27,11 @@ "update": "更新" }, "user-scrobble-history": { - "title": "", - "description": "", - "filter-label": "筛选器", - "created-header": "创建", - "last-modified-header": "上次验证", + "title": "Scrobble历史", + "description": "在这里您将找到与您的帐户关联的所有追踪记录事件。为了确保记录存在,您必须配置一个可用的追踪记录服务器。所有已处理的事件将在 1 个月后清除。如果存在未处理的事件,很可能这些事件无法与追踪记录进行匹配。请联系您的管理员进行修正。", + "filter-label": "筛选", + "created-header": "新建", + "last-modified-header": "最近修改", "type-header": "类型", "series-header": "系列", "data-header": "数据", @@ -40,14 +40,14 @@ "volume-and-chapter-num": "{{v}} 卷 {{n}} 话", "rating": "评分 {{r}}", "not-applicable": "不适用", - "processed": "处理", + "processed": "已处理", "not-processed": "尚未处理" }, "scrobble-event-type-pipe": { "chapter-read": "阅读进度", "score-updated": "评分更新", - "want-to-read-add": "准备阅读:添加", - "want-to-read-remove": "准备阅读:移除", + "want-to-read-add": "想读:加入", + "want-to-read-remove": "想读:移除", "review": "评论更新" }, "spoiler": { @@ -67,16 +67,16 @@ "go-to-review": "跳转评论" }, "review-card": { - "your-review": "你的评论", + "your-review": "我的评论", "external-review": "外部评论", "local-review": "评论", "rating-percentage": "评分 {{r}}%" }, "want-to-read": { - "title": "准备阅读", - "series-count": "", - "no-items": "尚无项目,试着添加系列。", - "no-items-filtered": "没有项目与当前筛选器匹配。" + "title": "想读", + "series-count": "{{common.series-count}}", + "no-items": "没有条目,请尝试添加一个系列。", + "no-items-filtered": "当前筛选没有匹配的条目。" }, "user-preferences": { "title": "用户面板", @@ -87,12 +87,12 @@ "theme-tab": "主题", "devices-tab": "设备", "stats-tab": "状态", - "scrobbling-tab": "", + "scrobbling-tab": "Scrobbling", "success-toast": "用户首选项已更新", "global-settings-title": "全局设置", "page-layout-mode-label": "页面显示模式", - "page-layout-mode-tooltip": "在系列详细信息页面上将项目显示为卡片或列表视图。", - "locale-label": "", + "page-layout-mode-tooltip": "在系列详细信息页面采用卡片或列表视图。", + "locale-label": "本地语言", "locale-tooltip": "Kavita 当前语言", "blur-unread-summaries-label": "模糊未读摘要", "blur-unread-summaries-tooltip": "模糊没有阅读进度的卷或章节的摘要文本(以避免剧透)", @@ -100,1581 +100,1659 @@ "prompt-on-download-tooltip": "当下载大小超过 {{size}}MB 时提示", "disable-animations-label": "关闭动画", "disable-animations-tooltip": "关闭站点中的动画。对电子墨水阅读器很有用。", - "collapse-series-relationships-label": "", + "collapse-series-relationships-label": "折叠系列关系", "collapse-series-relationships-tooltip": "Kavitra 是否显示没有关系的系列或者是父/前传", "share-series-reviews-label": "分享系列评论", - "share-series-reviews-tooltip": "Kavita 是否对其他用户的系列评论显示你的评论", + "share-series-reviews-tooltip": "是否对其他用户显示你的评论", "image-reader-settings-title": "图像阅读器", "reading-direction-label": "阅读方向", - "reading-direction-tooltip": "", + "reading-direction-tooltip": "单击方向移动到下一页。从右到左意味着您单击屏幕左侧以移至下一页。", "scaling-option-label": "缩放选项", - "scaling-option-tooltip": "", - "page-splitting-label": "", - "page-splitting-tooltip": "", - "reading-mode-label": "", - "layout-mode-label": "", - "layout-mode-tooltip": "", + "scaling-option-tooltip": "如何将图像缩放到屏幕上。", + "page-splitting-label": "页面分割", + "page-splitting-tooltip": "如何分割全宽图像(即左右图像合并)", + "reading-mode-label": "阅读模式", + "layout-mode-label": "布局模式", + "layout-mode-tooltip": "将单个图像或两个并排图像渲染到屏幕上", "background-color-label": "背景色", - "auto-close-menu-label": "", - "show-screen-hints-label": "", - "emulate-comic-book-label": "", - "swipe-to-paginate-label": "", - "book-reader-settings-title": "", - "tap-to-paginate-label": "", - "tap-to-paginate-tooltip": "", - "immersive-mode-label": "", - "immersive-mode-tooltip": "", - "reading-direction-book-label": "", - "reading-direction-book-tooltip": "", - "font-family-label": "", - "font-family-tooltip": "", - "writing-style-label": "", - "writing-style-tooltip": "", - "layout-mode-book-label": "", - "layout-mode-book-tooltip": "", - "color-theme-book-label": "", - "color-theme-book-tooltip": "", - "font-size-book-label": "", - "line-height-book-label": "", - "line-height-book-tooltip": "", - "margin-book-label": "", - "margin-book-tooltip": "", - "clients-opds-alert": "", - "clients-opds-description": "", - "clients-api-key-tooltip": "", - "clients-opds-url-tooltip": "", - "reset": "", - "save": "" + "auto-close-menu-label": "自动关闭菜单", + "show-screen-hints-label": "显示屏幕提示", + "emulate-comic-book-label": "模仿漫画书", + "swipe-to-paginate-label": "滑动翻页", + "book-reader-settings-title": "图书阅读器", + "tap-to-paginate-label": "点击翻页", + "tap-to-paginate-tooltip": "是否允许点击图书阅读器屏幕的两侧翻页", + "immersive-mode-label": "沉浸模式", + "immersive-mode-tooltip": "点击阅读器的文档后隐藏菜单并打开“点击翻页”功能", + "reading-direction-book-label": "阅读方向", + "reading-direction-book-tooltip": "单击方向移动到下一页。从右到左意味着您单击屏幕左侧以移至下一页。", + "font-family-label": "字体", + "font-family-tooltip": "要加载的字体,默认加载图书的默认字体", + "writing-style-label": "书籍排版", + "writing-style-tooltip": "更改书籍排版方向。横向是从左到右,竖向是从上到下。", + "layout-mode-book-label": "布局模式", + "layout-mode-book-tooltip": "确定内容如何布局,滚屏就像把书塞满屏幕,单列或双列匹配设备屏幕的高度且每个页面容纳单列或双列文本", + "color-theme-book-label": "主题颜色", + "color-theme-book-tooltip": "图书阅读器目录和菜单的主题颜色", + "font-size-book-label": "字体大小", + "line-height-book-label": "行间距", + "line-height-book-tooltip": "图书中每行之间的间距", + "margin-book-label": "页边距", + "margin-book-tooltip": "屏幕两侧的间距,移动设备与此设置无关,间距固定为0", + "clients-opds-alert": "此服务器未启用OPDS,不会影响Tachiyomi用户。", + "clients-opds-description": "所有第三方客户端均使用下方的API密钥或链接。它们就像密码一样,请保密。", + "clients-api-key-tooltip": "API密钥等同于密码,请保持它的机密性和安全性。", + "clients-opds-url-tooltip": "OPDS URL", + "reset": "{{common.reset}}", + "save": "{{common.save}}" }, "user-holds": { - "title": "", - "description": "" + "title": "刮削暂停", + "description": "这是一个由用户自主管理的系列列表,这些系列将不会被同步到你设定的刮削器记录中。您可以随时移除一个系列在下一个可刮削的事件(阅读进度、评分、想读状态)时触发事件。" }, "theme-manager": { - "title": "", - "looking-for-theme": "", - "looking-for-theme-continued": "", - "scan": "", - "site-themes": "", - "set-default": "", - "apply": "", - "applied": "", - "updated-toastr": "", - "scan-queued": "" + "title": "主题管理器", + "looking-for-theme": "您是在寻找一个明亮的或者墨水屏主题吗?我们有一些自定义主题供您使用。 ", + "looking-for-theme-continued": "Github主题", + "scan": "扫描", + "site-themes": "网站主题", + "set-default": "设为默认值", + "apply": "{{common.apply}}", + "applied": "已应用", + "updated-toastr": "网站默认值已更新为{{name}}", + "scan-queued": "网站主题扫描已进入队列" }, "theme": { - "theme-dark": "", - "theme-black": "", - "theme-paper": "", - "theme-white": "" + "theme-dark": "黑暗", + "theme-black": "黑色", + "theme-paper": "纸张", + "theme-white": "白色" }, "restriction-selector": { - "title": "", - "description": "", - "not-applicable-for-admins": "", - "age-rating-label": "", - "no-restriction": "", - "include-unknowns-label": "", - "include-unknowns-tooltip": "" + "title": "年龄分级限制", + "description": "选择后,全部系列和至少存在一个条目的阅读清单的年龄限制高于下面选择的,将从结果中移除。", + "not-applicable-for-admins": "不适用于管理员。", + "age-rating-label": "年龄分级", + "no-restriction": "无限制", + "include-unknowns-label": "包含未知内容", + "include-unknowns-tooltip": "选中此项,那么未知内容将在年龄限制下被允许。这可能导致未标记的媒体泄露给有年龄限制的用户。" }, "site-theme-provider-pipe": { - "system": "", - "user": "" + "system": "系统", + "user": "用户" }, "manage-devices": { - "title": "", - "description": "", - "devices-title": "", - "no-devices": "", - "platform-label": "", - "email-label": "", - "add": "", - "delete": "", - "edit": "" + "title": "设备管理", + "description": "对于无法通过浏览器访问Kavita的设备,您可以设置一个电子邮件地址用来接收文件。", + "devices-title": "设备", + "no-devices": "尚未设置任何设备", + "platform-label": "平台: ", + "email-label": "电子邮件: ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" }, "edit-device": { - "device-name-label": "", - "email-label": "", - "email-tooltip": "", - "device-platform-label": "", - "save": "", - "required-field": "", - "valid-email": "" + "device-name-label": "设备名称", + "email-label": "{{common.email}}", + "email-tooltip": "此电子邮件地址用于接收通过“发送到”菜单发送的文件", + "device-platform-label": "设备平台", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" }, "change-password": { - "password-label": "", - "current-password-label": "", - "new-password-label": "", - "confirm-password-label": "", - "reset": "", - "edit": "", - "cancel": "", - "save": "", - "required-field": "", - "passwords-must-match": "", - "permission-error": "" + "password-label": "{{common.password}}", + "current-password-label": "当前密码", + "new-password-label": "新密码", + "confirm-password-label": "确认密码", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "密码必须一致", + "permission-error": "您无权更改密码,请与服务器管理员联系。" }, "change-email": { - "email-label": "", - "current-password-label": "", - "email-not-confirmed": "", - "email-updated-title": "", - "email-updated-description": "", - "setup-user-account": "", - "invite-url-label": "", - "invite-url-tooltip": "", - "permission-error": "", - "required-field": "", - "reset": "", - "edit": "", - "cancel": "", - "save": "" + "email-label": "{{common.email}}", + "current-password-label": "当前密码", + "email-not-confirmed": "此电子邮件未确认", + "email-updated-title": "电子邮件已更新", + "email-updated-description": "您可以使用下面的链接确认您账户的电子邮件。如果您的服务器可以外部访问,一封电子邮件将发送到该电子邮件地址,并且您可以使用该链接来确认电子邮件。", + "setup-user-account": "设置用户帐户", + "invite-url-label": "邀请链接", + "invite-url-tooltip": "将其复制并粘贴到一个新的标签页中", + "permission-error": "您无权更改电子邮件,请与服务器管理员联系。", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "change-age-restriction": { - "age-restriction-label": "", - "unknowns": "", - "reset": "", - "edit": "", - "cancel": "", - "save": "" + "age-restriction-label": "年龄限制", + "unknowns": "未知", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "api-key": { - "copy": "", - "regen-warning": "", - "no-key": "", - "confirm-reset": "", - "key-reset": "" + "copy": "复制", + "regen-warning": "重新生成您的API密钥将使任何现有客户端失效。", + "no-key": "错误-未设置密钥", + "confirm-reset": "所有OPDS配置将会失效,您确定继续吗?", + "key-reset": "API密钥已重置", + "show": "显示" }, "scrobbling-providers": { - "title": "", - "requires": "", - "token-expired": "", - "no-token-set": "", - "token-set": "", - "generate": "", - "instructions": "", - "token-input-label": "", - "edit": "", - "cancel": "", - "save": "" + "title": "刮削提供", + "requires": "此功能需要一个有效的{{product}}许可证", + "token-expired": "Token已过期", + "no-token-set": "未设置Token", + "token-set": "设置Token", + "generate": "生成", + "instructions": "首次使用Kavita+的用户应点击下面的 \"{{scrobbling-providers.generate}}\"以允许Kavita+与{{service}}对话。授权程序后,将token 复制并粘贴到下面的输入栏中。您可以随时重新生成token 。", + "token-input-label": "{{service}} Token在这里填写", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "typeahead": { - "locked-field": "", - "close": "", - "loading": "", - "add-item": "", - "no-data": "", - "add-custom-item": "" + "locked-field": "字段已锁定", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "add-item": "添加 {{item}}…", + "no-data": "无数据", + "add-custom-item": ", 输入以添加自定义项目" }, "generic-list-modal": { - "close": "", - "clear": "", - "filter": "", - "open-filtered-search": "" + "close": "{{common.close}}", + "clear": "清除", + "filter": "筛选", + "open-filtered-search": "为{{item}}打开一个带有筛选器的搜索" }, "user-stats-info-cards": { - "total-pages-read-label": "", - "total-pages-read-tooltip": "", - "total-words-read-label": "", - "total-words-read-tooltip": "", - "time-spent-reading-label": "", - "time-spent-reading-tooltip": "", - "chapters-read-label": "", - "chapters-read-tooltip": "", - "avg-reading-per-week-label": "", - "last-active-label": "", - "chapters": "" + "total-pages-read-label": "总阅读页数", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", + "total-words-read-label": "总阅读字数", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "阅读时长", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "阅读章节", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "avg-reading-per-week-label": "平均阅读时长/每周", + "last-active-label": "最后活跃时间", + "chapters": "{{value}} 章节" }, "user-stats": { - "library-read-progress-title": "", - "read-percentage": "" + "library-read-progress-title": "资料库阅读进度", + "read-percentage": "% 已阅读" }, "top-readers": { - "title": "", - "time-selection-label": "", - "comics-label": "", - "manga-label": "", - "books-label": "", - "this-week": "", - "last-7-days": "", - "last-30-days": "", - "last-90-days": "", - "last-year": "", - "all-time": "" + "title": "热门读者", + "time-selection-label": "时间范围", + "comics-label": "美漫:{{value}} 小时", + "manga-label": "日漫:{{value}} 小时", + "books-label": "图书:{{value}}小时", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" }, "role-selector": { - "title": "" + "title": "角色" }, "directory-picker": { - "title": "", - "close": "", - "path-label": "", - "path-placeholder": "", - "instructions": "", - "type-header": "", - "name-header": "", - "cancel": "", - "share": "", - "help": "" + "title": "选择目录", + "close": "{{common.close}}", + "path-label": "路径", + "path-placeholder": "输入或选择路径", + "instructions": "选择一个文件夹以查看面包屑导航。没有找到您的目录?请尝试先检查“/”。", + "type-header": "类型", + "name-header": "名称", + "cancel": "{{common.cancel}}", + "share": "共享", + "help": "{{common.help}}" }, "library-access-modal": { - "select-all": "", - "deselect-all": "", - "title": "", - "close": "", - "reset": "", - "cancel": "", - "save": "", - "no-data": "" + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "资料库访问", + "close": "{{common.close}}", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "no-data": "尚未设置资料库。" }, "time-periods": { - "this-week": "", - "last-7-days": "", - "last-30-days": "", - "last-90-days": "", - "last-year": "", - "all-time": "" + "this-week": "本周", + "last-7-days": "最近7天", + "last-30-days": "最近30天", + "last-90-days": "最近90天", + "last-year": "最近一年", + "all-time": "所有时间" }, "device-platform-pipe": { - "custom": "" + "custom": "自定义" }, "day-of-week-pipe": { - "monday": "", - "tuesday": "", - "wednesday": "", - "thursday": "", - "friday": "", - "saturday": "", - "sunday": "" + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday": "星期日" }, "cbl-import-result-pipe": { - "success": "", - "partial": "", - "failure": "" + "success": "成功", + "partial": "部分", + "failure": "失败" }, "cbl-conflict-reason-pipe": { - "all-series-missing": "", - "chapter-missing": "", - "empty-file": "", - "name-conflict": "", - "series-collision": "", - "series-missing": "", - "volume-missing": "", - "all-chapter-missing": "", - "invalid-file": "", - "success": "" + "all-series-missing": "您的账户无法访问列表中的所有系列,或者Kavita在列表中没有任何内容。", + "chapter-missing": "{{series}}: Kavita缺少第{{chapter}}章。此项目将被跳过。", + "empty-file": "cbl文件为空,没有任何操作可以进行。", + "name-conflict": "您的账户已经存在一个与cbl文件匹配的阅读列表({{readingListName}})。", + "series-collision": "系列{{seriesLink}}与另一个资料库中同名的系列发生冲突。", + "series-missing": "系列{{series}}在Kavita中缺失,或者您的账户没有权限。所有带有此系列的项目将在导入时被跳过。", + "volume-missing": "{{series}}:卷{{volume}}在Kavita中缺失,或者您的账户没有权限。所有带有此卷号的项目将被跳过。", + "all-chapter-missing": "无法将所有章节与Kavita中的章节匹配。", + "invalid-file": "文件已损坏或与预期的标签/规范不匹配。", + "success": "成功映射{{series}}卷{{volume}}章节{{chapter}}。" }, "time-duration-pipe": { - "hours": "", - "minutes": "", - "days": "", - "months": "", - "years": "" + "hours": "{{value}} 小时", + "minutes": "{{value}} 分", + "days": "{{value}} 天", + "months": "{{value}} 月", + "years": "{{value}} 年" }, "time-ago-pipe": { - "just-now": "", - "min-ago": "", - "mins-ago": "", - "hour-ago": "", - "hours-ago": "", - "day-ago": "", - "days-ago": "", - "month-ago": "", - "months-ago": "", - "year-ago": "", - "years-ago": "" + "just-now": "刚刚", + "min-ago": "一分钟前", + "mins-ago": "{{value}}分钟前", + "hour-ago": "一小时前", + "hours-ago": "{{value}}小时前", + "day-ago": "一天前", + "days-ago": "{{value}}天前", + "month-ago": "一个月前", + "months-ago": "{{value}}个月前", + "year-ago": "一年前", + "years-ago": "{{value}}年前", + "never": "从不" }, "relationship-pipe": { - "adaptation": "", - "alternative-setting": "", - "alternative-version": "", - "character": "", - "contains": "", - "doujinshi": "", - "other": "", - "prequel": "", - "sequel": "", - "side-story": "", - "spin-off": "", - "parent": "", - "edition": "" + "adaptation": "改编", + "alternative-setting": "另类设定", + "alternative-version": "另类版本", + "character": "角色", + "contains": "包含", + "doujinshi": "同人", + "other": "其他", + "prequel": "前传", + "sequel": "续篇", + "side-story": "支线", + "spin-off": "外传", + "parent": "原作", + "edition": "版本" }, "publication-status-pipe": { - "ongoing": "", - "hiatus": "", - "completed": "", - "cancelled": "", - "ended": "" + "ongoing": "连载中", + "hiatus": "停更", + "completed": "完结", + "cancelled": "中止", + "ended": "终止" }, "person-role-pipe": { - "artist": "", - "character": "", - "colorist": "", - "cover-artist": "", - "editor": "", - "inker": "", - "letterer": "", - "penciller": "", - "publisher": "", - "writer": "", - "other": "" + "artist": "设计师", + "character": "角色", + "colorist": "上色师", + "cover-artist": "封面设计", + "editor": "编辑", + "inker": "上墨师", + "letterer": "排版", + "penciller": "线稿师", + "publisher": "出版社", + "writer": "作者", + "other": "其他" }, "manga-format-pipe": { - "epub": "", - "archive": "", - "image": "", - "pdf": "", - "unknown": "" + "epub": "EPUB", + "archive": "档案", + "image": "图像", + "pdf": "PDF", + "unknown": "未知" }, "library-type-pipe": { - "book": "", - "comic": "", - "manga": "" + "book": "图书", + "comic": "美漫", + "manga": "日漫" }, "age-rating-pipe": { - "unknown": "", - "early-childhood": "", - "adults-only": "", - "everyone": "", - "everyone-10-plus": "", - "g": "", - "kids-to-adults": "", - "mature": "", - "ma15-plus": "", - "mature-17-plus": "", - "rating-pending": "", - "teen": "", - "x18-plus": "", - "not-applicable": "", - "pg": "", - "r18-plus": "" + "unknown": "未知", + "early-childhood": "幼儿", + "adults-only": "仅限成人 18+", + "everyone": "所有人", + "everyone-10-plus": "10岁及以上", + "g": "G", + "kids-to-adults": "适合儿童至成人", + "mature": "成人", + "ma15-plus": "MA15+", + "mature-17-plus": "Mature 17+", + "rating-pending": "分级待定", + "teen": "青少年", + "x18-plus": "X18+", + "not-applicable": "不适用", + "pg": "PG", + "r18-plus": "R18+" }, "reset-password": { - "title": "", - "description": "", - "email-label": "", - "required-field": "", - "valid-email": "", - "submit": "" + "title": "重置密码", + "description": "请输入您账户的电子邮件地址。如果电子邮件地址有效,Kavita将发送一封邮件给您,否则,请向管理员索取日志中的链接。", + "email-label": "{{common.email}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "submit": "{{common.submit}}" }, "reset-password-modal": { - "title": "", - "new-password-label": "", - "error-label": "", - "close": "", - "cancel": "", - "save": "" + "title": "重置{{username}}的密码", + "new-password-label": "新密码", + "error-label": "错误: ", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" }, "all-series": { - "series-count": "" + "series-count": "{{common.series-count}}", + "title": "所有系列" }, "announcements": { - "title": "" + "title": "公告" }, "changelog": { - "installed": "", - "download": "", - "published-label": "", - "available": "", - "description": "", - "description-continued": "" + "installed": "已安装", + "download": "下载", + "published-label": "出版: ", + "available": "可用", + "description": "如果您没有看到{{installed}}", + "description-continued": "标签,您正在使用测试版。只有稳定版才会显示为可用。" }, "invite-user": { - "title": "", - "close": "", - "description": "", - "email": "", - "required-field": "", - "setup-user-title": "", - "setup-user-description": "", - "setup-user-account": "", - "setup-user-account-tooltip": "", - "invite-url-label": "", - "invite": "", - "inviting": "", - "cancel": "" + "title": "邀请用户", + "close": "{{common.close}}", + "description": "邀请用户加入,请输入用户的电子邮件地址,我们将发送一封电子邮件帮助他们创建账户。如果您不希望使用我们的电子邮件服务,您可以搭建自托管的电子邮件服务或使用一个虚假的电子邮件地址(忘记用户将无法登录)。无论如何,都会提供一个用于手动设置账户的链接。", + "email": "{{common.email}}", + "required-field": "{{common.required-field}}", + "setup-user-title": "用户已邀请", + "setup-user-description": "您可以使用下方的链接设置用户账户,使用链接注册新用户之前可能需要先注销。如果您的服务器可以从外部访问,则向用户发送一封电子邮件,用户可通过邮件中的链接完成账户设置。", + "setup-user-account": "设置用户账户", + "setup-user-account-tooltip": "复制此内容并粘贴到新选项卡中,您可能需要注销。", + "invite-url-label": "邀请链接", + "invite": "邀请", + "inviting": "正在邀请…", + "cancel": "{{common.cancel}}" }, "library-selector": { - "title": "", - "select-all": "", - "deselect-all": "", - "no-data": "" + "title": "资料库", + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "no-data": "尚未设置资料库。" }, "license": { - "title": "", - "manage": "", - "invalid-license-tooltip": "", - "check": "", - "cancel": "", - "edit": "", - "buy": "", - "activate": "", - "renew": "", - "no-license-key": "", - "license-valid": "", - "license-not-valid": "", - "loading": "", - "activate-description": "", - "activate-license-label": "", - "activate-email-label": "", - "activate-delete": "", - "activate-save": "" + "title": "Kavita+许可证", + "manage": "管理", + "invalid-license-tooltip": "如果订阅已终止,则必须向支持人员发送电子邮件才能创建新订阅", + "check": "检查", + "cancel": "{{common.cancel}}", + "edit": "{{common.edit}}", + "buy": "购买", + "activate": "激活", + "renew": "续订", + "no-license-key": "无许可证密钥", + "license-valid": "许可证有效", + "license-not-valid": "许可证无效", + "loading": "{{common.loading}}", + "activate-description": "输入用于注册 Stripe 的许可证密钥和电子邮件", + "activate-license-label": "许可证密钥", + "activate-email-label": "{{common.email}}", + "activate-delete": "删除", + "activate-save": "{{common.save}}" }, "book-line-overlay": { - "copy": "", - "bookmark": "", - "close": "", - "required-field": "", - "bookmark-label": "", - "save": "" + "copy": "复制", + "bookmark": "书签", + "close": "{{common.close}}", + "required-field": "{{common.required-field}}", + "bookmark-label": "书签名称", + "save": "{{common.save}}" }, "book-reader": { - "title": "", - "page-label": "", - "pagination-header": "", - "go-to-page": "", - "go-to-last-page": "", - "prev-page": "", - "next-page": "", - "prev-chapter": "", - "next-chapter": "", - "skip-header": "", - "virtual-pages": "", - "settings-header": "", - "table-of-contents-header": "", - "bookmarks-header": "", - "toc-header": "", - "loading-book": "", - "go-back": "", - "incognito-mode-alt": "", - "incognito-mode-label": "", - "next": "", - "previous": "" + "title": "图书设置", + "page-label": "页", + "pagination-header": "节", + "go-to-page": "转到页面", + "go-to-last-page": "转到最后一页", + "prev-page": "上一页", + "next-page": "下一页", + "prev-chapter": "上一话/卷", + "next-chapter": "下一话/卷", + "skip-header": "跳转至主要内容", + "virtual-pages": "虚拟页面", + "settings-header": "设置", + "table-of-contents-header": "目录", + "bookmarks-header": "书签", + "toc-header": "大纲", + "loading-book": "正在加载书籍…", + "go-back": "返回", + "incognito-mode-alt": "隐身模式已开启,点击关闭。", + "incognito-mode-label": "隐身模式", + "next": "下一个", + "previous": "上一个", + "go-to-page-prompt": "一共{{totalPages}}页,您想跳转至哪一页?" }, "personal-table-of-contents": { - "no-data": "", - "page": "", - "delete": "" + "no-data": "尚未添加书签", + "page": "页 {{value}}", + "delete": "删除{{bookmarkName}}" }, "confirm-email": { - "title": "", - "description": "", - "error-label": "", - "username-label": "", - "password-label": "", - "email-label": "", - "required-field": "", - "valid-email": "", - "password-validation": "", - "register": "" + "title": "注册", + "description": "请完善表格以完成注册程序", + "error-label": "错误: ", + "username-label": "{{common.username}}", + "password-label": "{{common.password}}", + "email-label": "{{common.email}}", + "required-field": "{{common.required-field}}", + "valid-email": "{{common.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "注册" }, "confirm-email-change": { - "title": "", - "non-confirm-description": "", - "confirm-description": "", - "success": "" + "title": "验证电子邮件", + "non-confirm-description": "请稍等,您更新后的电子邮地址正在进行验证。", + "confirm-description": "您在Kavita中的电子邮件地址已更改并且已经完成验证。您将被重定向到登录页面。", + "success": "成功!" }, "confirm-reset-password": { - "title": "", - "description": "", - "password-label": "", - "required-field": "", - "submit": "", - "password-validation": "" + "title": "密码重置", + "description": "输入新密码", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "submit": "{{common.submit}}", + "password-validation": "{{validation.password-validation}}" }, "register": { - "title": "", - "description": "", - "username-label": "", - "email-label": "", - "email-tooltip": "", - "password-label": "", - "required-field": "", - "valid-email": "", - "password-validation": "", - "register": "" + "title": "注册", + "description": "请完善表单以注册管理员账户", + "username-label": "{{common.username}}", + "email-label": "{{common.email}}", + "email-tooltip": "电子邮件地址不需要是真实的,但它可以提供访问忘记密码的功能。除非在没有自定义电子邮件服务主机的情况下使用了忘记密码功能,否则不会将其发送到服务器外部。", + "password-label": "{{common.password}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "password-validation": "{{validation.password-validation}}", + "register": "注册" }, "series-detail": { - "page-settings-title": "", - "close": "", - "layout-mode-label": "", - "layout-mode-option-card": "", - "layout-mode-option-list": "", - "continue-from": "", - "read": "", - "continue": "", - "read-options-alt": "", - "incognito": "", - "remove-from-want-to-read": "", - "add-to-want-to-read": "", - "edit-series-alt": "", - "download-series--tooltip": "", - "downloading-status": "", - "user-reviews-alt": "", - "storyline-tab": "", - "books-tab": "", - "volumes-tab": "", - "specials-tab": "", - "related-tab": "", - "recommendations-tab": "", - "send-to": "", - "no-pages": "", - "no-chapters": "", - "cover-change": "" + "page-settings-title": "页面设置", + "close": "{{common.close}}", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-option-card": "卡片", + "layout-mode-option-list": "列表", + "continue-from": "继续{{title}}", + "read": "{{common.read}}", + "continue": "继续", + "read-options-alt": "阅读选项", + "incognito": "隐身模式", + "remove-from-want-to-read": "从想读中移除", + "add-to-want-to-read": "添加到想读", + "edit-series-alt": "编辑系列信息", + "download-series--tooltip": "下载系列", + "downloading-status": "下载中…", + "user-reviews-alt": "用户评论", + "storyline-tab": "正传故事线", + "books-tab": "书籍", + "volumes-tab": "卷", + "specials-tab": "特刊", + "related-tab": "相关", + "recommendations-tab": "建议", + "send-to": "文件已通过电子邮件发送到{{deviceName}}", + "no-pages": "{{toasts.no-pages}}", + "no-chapters": "本卷没有章节,无法读取。", + "cover-change": "浏览器刷新图片可能需要一分钟的时间。在此期间,某些页面可能仍显示旧的图片。" }, "series-metadata-detail": { - "links-title": "", - "genres-title": "", - "tags-title": "", - "collections-title": "", - "reading-lists-title": "", - "writers-title": "", - "cover-artists-title": "", - "characters-title": "", - "colorists-title": "", - "editors-title": "", - "inkers-title": "", - "letterers-title": "", - "translators-title": "", - "pencillers-title": "", - "publishers-title": "", - "promoted": "", - "see-more": "", - "see-less": "" + "links-title": "链接", + "genres-title": "类别", + "tags-title": "标签", + "collections-title": "{{side-nav.collections}}", + "reading-lists-title": "{{side-nav.reading-lists}}", + "writers-title": "作者", + "cover-artists-title": "封面设计", + "characters-title": "字数", + "colorists-title": "调色师", + "editors-title": "编者", + "inkers-title": "着墨师", + "letterers-title": "排版", + "translators-title": "译者", + "pencillers-title": "铅笔师", + "publishers-title": "出版社", + "promoted": "{{common.promoted}}", + "see-more": "查看更多", + "see-less": "收起" }, "badge-expander": { - "more-items": "" + "more-items": "和{{count}}个更多" }, "read-more": { - "read-more": "", - "read-less": "" + "read-more": "查看全文", + "read-less": "收起" }, "update-notification-modal": { - "title": "", - "close": "", - "help": "", - "download": "" + "title": "发现新版本!", + "close": "{{common.close}}", + "help": "如何更新", + "download": "下载" }, "side-nav-companion-bar": { - "page-settings-title": "", - "open-filter-and-sort": "", - "close-filter-and-sort": "", - "filter-and-sort-alt": "" + "page-settings-title": "{{series-detail.page-settings-title}}", + "open-filter-and-sort": "打开筛选和排序", + "close-filter-and-sort": "关闭筛选和排序", + "filter-and-sort-alt": "排序/筛选" }, "side-nav": { - "home": "", - "want-to-read": "", - "collections": "", - "reading-lists": "", - "bookmarks": "", - "filter-label": "", - "all-series": "", - "clear": "", - "donate": "" + "home": "主页", + "want-to-read": "想读", + "collections": "收藏", + "reading-lists": "阅读清单", + "bookmarks": "书签", + "filter-label": "筛选", + "all-series": "所有系列", + "clear": "清空", + "donate": "捐助" }, "library-settings-modal": { - "close": "", - "edit-title": "", - "add-title": "", - "general-tab": "", - "folder-tab": "", - "cover-tab": "", - "advanced-tab": "", - "name-label": "", - "library-name-unique": "", - "last-scanned-label": "", - "type-label": "", - "type-tooltip": "", - "folder-description": "", - "browse": "", - "help-us-part-1": "", - "help-us-part-2": "", - "help-us-part-3": "", - "naming-conventions-part-1": "", - "naming-conventions-part-2": "", - "naming-conventions-part-3": "", - "cover-description": "", - "cover-description-extra": "", - "manage-collection-label": "", - "manage-collection-tooltip": "", - "manage-reading-list-label": "", - "manage-reading-list-tooltip": "", - "allow-scrobbling-label": "", - "allow-scrobbling-tooltip": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "include-in-dashboard-label": "", - "include-in-dashboard-tooltip": "", - "include-in-recommendation-label": "", - "include-in-recommendation-tooltip": "", - "include-in-search-label": "", - "include-in-search-tooltip": "", - "force-scan": "", - "force-scan-tooltip": "", - "reset": "", - "cancel": "", - "next": "", - "save": "", - "required-field": "" + "close": "{{common.close}}", + "edit-title": "修改{{name}}", + "add-title": "添加资料库", + "general-tab": "常规", + "folder-tab": "文件夹", + "cover-tab": "封面", + "advanced-tab": "高级", + "name-label": "名称", + "library-name-unique": "资料库名称必须唯一", + "last-scanned-label": "上次扫描:", + "type-label": "类型", + "type-tooltip": "资料库类型决定了文件名的解析方式,以及在UI中是否显示章节(日漫)或期(美漫)。图书与日漫相同,但在UI中命名方式有所不同。", + "folder-description": "将文件夹添加到您的资料库中", + "browse": "浏览媒体文件夹", + "help-us-part-1": "通过遵循我们的 ", + "help-us-part-2": "命名指南", + "help-us-part-3": "命名和组织您的媒体。", + "naming-conventions-part-1": "Kavita资料库的文件夹命名必须符合 ", + "naming-conventions-part-2": "文件夹要求。", + "naming-conventions-part-3": "请查看此链接确保您正在遵守要求,否则执行扫描后文件无法正确显示。", + "cover-description": "自定义资料库的图标是可选项", + "cover-description-extra": "资料库图片不要过大,建议尺寸为32x32像素。Kavita不验证图片文件的尺寸。", + "manage-collection-label": "管理收藏", + "manage-collection-tooltip": "是否允许Kavita根据ComicInfo.xml/opf文件中的SeriesGroup标签创建收藏", + "manage-reading-list-label": "管理阅读列表", + "manage-reading-list-tooltip": "是否允许Kavita根据ComicInfo.xml/opf文件中的StoryArc/StoryArcNumber和AlternativeSeries/AlternativeCount标签创建阅读清单", + "allow-scrobbling-label": "允许跟踪记录", + "allow-scrobbling-tooltip": "是否允许Kavita将阅读事件、想读状态、评分和评论记录至已配置的服务提供商?本功能仅在服务器的Kavita+订阅激活时才会生效。", + "folder-watching-label": "监控文件夹", + "folder-watching-tooltip": "覆盖此资料库服务器的监控文件夹设置。如果关闭此功能,则不会监控此资料库包含的文件夹。如果共享资料库的文件夹,那么文件夹仍可能被监控。", + "include-in-dashboard-label": "在仪表板中显示", + "include-in-dashboard-tooltip": "是否在仪表板上显示此资料库的系列。这会影响所有数据流,例如最近阅读、最近更新、最近添加或其他自定义内容。", + "include-in-recommendation-label": "在推荐中显示", + "include-in-recommendation-tooltip": "是否在推荐页面中显示此资料库的系列。", + "include-in-search-label": "在搜索中显示", + "include-in-search-tooltip": "是否在搜索结果中显示此资料库的系列和任何派生信息(类别、人物、文件)。", + "force-scan": "强制扫描", + "force-scan-tooltip": "对资料库强制扫描,即全新扫描", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "next": "下一项", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}" }, "reader-settings": { - "general-settings-title": "", - "font-family-label": "", - "font-size-label": "", - "line-spacing-label": "", - "margin-label": "", - "reset-to-defaults": "", - "reader-settings-title": "", - "reading-direction-label": "", - "right-to-left": "", - "left-to-right": "", - "horizontal": "", - "vertical": "", - "writing-style-label": "", - "writing-style-tooltip": "", - "tap-to-paginate-label": "", - "tap-to-paginate-tooltip": "", - "on": "", - "off": "", - "immersive-mode-label": "", - "immersive-mode-tooltip": "", - "fullscreen-label": "", - "fullscreen-tooltip": "", - "exit": "", - "enter": "", - "layout-mode-label": "", - "layout-mode-tooltip": "", - "layout-mode-option-scroll": "", - "layout-mode-option-1col": "", - "layout-mode-option-2col": "", - "color-theme-title": "", - "theme-dark": "", - "theme-black": "", - "theme-white": "", - "theme-paper": "" + "general-settings-title": "常规设置", + "font-family-label": "{{user-preferences.font-family-label}}", + "font-size-label": "{{user-preferences.font-size-book-label}}", + "line-spacing-label": "{{user-preferences.line-height-book-label}}", + "margin-label": "{{user-preferences.margin-book-label}}", + "reset-to-defaults": "恢复默认设置", + "reader-settings-title": "阅读器设置", + "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "right-to-left": "从右到左", + "left-to-right": "从左到右", + "horizontal": "横向", + "vertical": "竖向", + "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-tooltip": "更改书籍排版方向。横向是从左到右,竖向是从上到下。", + "tap-to-paginate-label": "点击翻页", + "tap-to-paginate-tooltip": "点击屏幕边缘进行翻页", + "on": "开", + "off": "关", + "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-tooltip": "点击阅读器的文档后隐藏菜单并打开“点击翻页”功能", + "fullscreen-label": "全屏", + "fullscreen-tooltip": "将阅读器设置为全屏模式", + "exit": "退出", + "enter": "打开", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-tooltip": "滚屏:镜像epub文件(通常每章节一个滚屏页面)
    单列:每次创建一个单独的虚拟页面
    双列:每次创建两个并排布置的虚拟页面", + "layout-mode-option-scroll": "滚屏", + "layout-mode-option-1col": "单列", + "layout-mode-option-2col": "双列", + "color-theme-title": "主题颜色", + "theme-dark": "黑暗", + "theme-black": "黑色", + "theme-white": "白色", + "theme-paper": "纸张" }, "table-of-contents": { - "no-data": "" + "no-data": "这本书没有在元数据或toc文件中设置目录" }, "bookmarks": { - "title": "", - "series-count": "", - "no-data": "", - "no-data-2": "", - "confirm-delete": "", - "confirm-single-delete": "", - "delete-success": "", - "delete-single-success": "" + "title": "{{side-nav.bookmarks}}", + "series-count": "{{common.series-count}}", + "no-data": "当前没有书签。请尝试创建", + "no-data-2": "一个。", + "confirm-delete": "您确定要清除多个系列的所有书签吗?此操作无法撤销。", + "confirm-single-delete": "您确定要清除{{seriesName}}的所有书签吗?此操作无法撤销。", + "delete-success": "已移除书签", + "delete-single-success": "已删除{{seriesName}}的书签" }, "bulk-operations": { - "title": "", - "items-selected": "", - "mark-as-unread": "", - "mark-as-read": "", - "deselect-all": "" + "title": "批量操作", + "items-selected": "{{num}}项已选中", + "mark-as-unread": "标记为未读", + "mark-as-read": "标记为已读", + "deselect-all": "{{common.deselect-all}}" }, "card-detail-drawer": { - "general-tab": "", - "metadata-tab": "", - "cover-tab": "", - "info-tab": "", - "no-summary": "", - "writers-title": "", - "genres-title": "", - "publishers-title": "", - "tags-title": "", - "not-defined": "", - "read": "", - "unread": "", - "files": "", - "pages": "", - "added": "", - "size": "" + "general-tab": "常规", + "metadata-tab": "元数据", + "cover-tab": "封面", + "info-tab": "信息", + "no-summary": "没有可用的摘要。", + "writers-title": "{{series-metadata-detail.writers-title}}", + "genres-title": "{{series-metadata-detail.genres-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "tags-title": "{{series-metadata-detail.tags-title}}", + "not-defined": "未定义", + "read": "{{common.read}}", + "unread": "未读", + "files": "文件", + "pages": "页面:", + "added": "添加:", + "size": "文件大小:" }, "card-detail-layout": { - "total-items": "" + "total-items": "总共{{count}}个条目" }, "card-item": { - "cannot-read": "" + "cannot-read": "无法阅读" }, "chapter-metadata-detail": { - "no-data": "", - "writers-title": "", - "publishers-title": "", - "characters-title": "", - "translators-title": "", - "letterers-title": "", - "colorists-title": "", - "inkers-title": "", - "pencillers-title": "", - "cover-artists-title": "", - "editors-title": "" + "no-data": "没有可用的元数据", + "writers-title": "{{series-metadata-detail.writers-title}}", + "publishers-title": "{{series-metadata-detail.publishers-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "translators-title": "{{series-metadata-detail.translators-title}}", + "letterers-title": "{{series-metadata-detail.letterers-title}}", + "colorists-title": "{{series-metadata-detail.colorists-title}}", + "inkers-title": "{{series-metadata-detail.inkers-title}}", + "pencillers-title": "{{series-metadata-detail.pencillers-title}}", + "cover-artists-title": "{{series-metadata-detail.cover-artists-title}}", + "editors-title": "{{series-metadata-detail.editors-title}}" }, "cover-image-chooser": { - "drag-n-drop": "", - "upload": "", - "upload-continued": "", - "url-label": "", - "load": "", - "back": "", - "reset-cover-tooltip": "", - "reset": "", - "image-num": "", - "apply": "", - "applied": "" + "drag-n-drop": "拖放", + "upload": "上传", + "upload-continued": "一张图片", + "url-label": "链接", + "load": "加载", + "back": "返回", + "reset-cover-tooltip": "重置封面图像", + "reset": "{{common.reset}}", + "image-num": "图片{{num}}", + "apply": "{{common.apply}}", + "applied": "{{theme-manager.applied}}" }, "download-indicator": { - "progress": "" + "progress": "{{percentage}}% 已下载" }, "edit-series-relation": { - "description-part-1": "", - "description-part-2": "", - "target-series": "", - "relationship": "", - "remove": "", - "add-relationship": "", - "parent": "" + "description-part-1": "不确定要添加什么关系?请查看我们的", + "description-part-2": "wiki。", + "target-series": "目标系列", + "relationship": "关系", + "remove": "移除", + "add-relationship": "添加关系", + "parent": "{{relationship-pipe.parent}}" }, "entity-info-cards": { - "tags-title": "", - "characters-title": "", - "release-date-title": "", - "release-date-tooltip": "", - "age-rating-title": "", - "length-title": "", - "pages-count": "", - "words-count": "", - "reading-time-title": "", - "date-added-title": "", - "size-title": "", - "id-title": "", - "links-title": "", - "isbn-title": "", - "last-read-title": "", - "less-than-hour": "", - "range-hours": "", - "hour": "", - "hours": "" + "tags-title": "{{series-metadata-detail.tags-title}}", + "characters-title": "{{series-metadata-detail.characters-title}}", + "release-date-title": "发行", + "release-date-tooltip": "发布日行", + "age-rating-title": "年龄分级", + "length-title": "篇幅", + "pages-count": "{{num}} 页", + "words-count": "{{num}} 字", + "reading-time-title": "阅读时长", + "date-added-title": "添加日期", + "size-title": "大小", + "id-title": "ID", + "links-title": "{{series-metadata-detail.links-title}}", + "isbn-title": "ISBN", + "last-read-title": "最近阅读", + "less-than-hour": "<1 小时", + "range-hours": "{{value}} {{hourWord}}", + "hour": "小时", + "hours": "小时", + "read-time-title": "{{series-info-cards.read-time-title}}" }, "series-info-cards": { - "release-date-title": "", - "release-year-tooltip": "", - "age-rating-title": "", - "language-title": "", - "publication-status-title": "", - "publication-status-tooltip": "", - "scrobbling-title": "", - "scrobbling-tooltip": "", - "on": "", - "off": "", - "disabled": "", - "format-title": "", - "last-read-title": "", - "length-title": "", - "read-time-title": "", - "less-than-hour": "", - "hour": "", - "hours": "", - "time-left-title": "", - "ongoing": "", - "pages-count": "", - "words-count": "" + "release-date-title": "{{entity-info-cards.release-date-title}}", + "release-year-tooltip": "发行年份", + "age-rating-title": "{{entity-info-cards.age-rating-title}}", + "language-title": "语言", + "publication-status-title": "出版", + "publication-status-tooltip": "出版状态", + "scrobbling-title": "刮削", + "scrobbling-tooltip": "记录状态", + "on": "开", + "off": "关", + "disabled": "已禁用", + "format-title": "格式", + "last-read-title": "上次阅读", + "length-title": "篇幅", + "read-time-title": "阅读时长", + "less-than-hour": "{{entity-info-cards.less-than-hour}}", + "hour": "{{entity-info-cards.hour}}", + "hours": "{{entity-info-cards.hours}}", + "time-left-title": "剩余时间", + "ongoing": "{{publication-status-pipe.ongoing}}", + "pages-count": "{{entity-info-cards.pages-count}}", + "words-count": "{{entity-info-cards.words-count}}" }, "bulk-add-to-collection": { - "title": "", - "promoted": "", - "close": "", - "filter-label": "", - "clear": "", - "no-data": "", - "loading": "", - "collection-label": "", - "create": "" + "title": "添加到收藏", + "promoted": "{{common.promoted}}", + "close": "{{common.close}}", + "filter-label": "筛选", + "clear": "{{common.clear}}", + "no-data": "尚未创建收藏", + "loading": "{{common.loading}}", + "collection-label": "收藏", + "create": "{{common.create}}" }, "entity-title": { - "special": "", - "issue-num": "", - "chapter": "" + "special": "特辑", + "issue-num": "问题 #", + "chapter": "章节" }, "external-series-card": { - "open-external": "" + "open-external": "打开外部链接" }, "list-item": { - "read": "" + "read": "{{common.read}}" }, "manage-alerts": { - "description-part-1": "", - "description-part-2": "", - "filter-label": "", - "clear-alerts": "", - "extension-header": "", - "file-header": "", - "comment-header": "", - "details-header": "" + "description-part-1": "此表格列出了扫描或读取媒体时发现的问题。此列表不受管理。您可以随时清除它,并使用扫描(强制)资料库来进行分析。一些常见错误及其释义的列表可以访问 ", + "description-part-2": "wiki。", + "filter-label": "筛选", + "clear-alerts": "清除警报", + "extension-header": "扩展名", + "file-header": "文件", + "comment-header": "备注", + "details-header": "详情" }, "manage-email-settings": { - "title": "", - "description": "", - "send-to-warning": "", - "email-url-label": "", - "email-url-tooltip": "", - "reset": "", - "test": "", - "host-name-label": "", - "host-name-tooltip": "", - "host-name-validation": "", - "reset-to-default": "", - "save": "" + "title": "电子邮件服务(SMTP)", + "description": "Kavita预设提供了一个电子邮件服务,用于邀请用户、重置密码等任务。通过我们的服务发送的电子邮件会立即被删除。您可以通过设置 {{link}} 服务来使用自己的电子邮件服务。设置电子邮件服务的URL,并使用测试按钮确保其正常工作。您随时可以将这些设置重置为默认值。无法禁用身份验证的电子邮件,尽管您不需要为用户使用有效的电子邮件地址。确认链接始终会保存到日志中,并在界面上呈现。如果您未通过公共可访问的URL访问Kavita,或者未配置主机名功能,则不会发送注册/确认电子邮件。", + "send-to-warning": "如果您希望\"发送到设备\"功能正常工作,您必须自己托管电子邮件服务。", + "email-url-label": "邮件服务器URL", + "email-url-tooltip": "请使用完整的URL地址来配置电子邮件服务,不要包含结尾的斜杠。", + "reset": "{{common.reset}}", + "test": "测试", + "host-name-label": "主机名", + "host-name-tooltip": "域名(反向代理)。如果设置了此项,始终使用此域名生成电子邮件。", + "host-name-validation": "主机名必须以http(s)开头,不能以 / 结尾", + "reset-to-default": "{{common.reset-to-default}}", + "save": "{{common.save}}" }, "manage-library": { - "title": "", - "add-library": "", - "no-data": "", - "loading": "", - "last-scanned-title": "", - "shared-folders-title": "", - "type-title": "", - "scan-library": "", - "delete-library": "", - "delete-library-by-name": "", - "edit-library": "", - "edit-library-by-name": "" + "title": "资料库", + "add-library": "添加资料库", + "no-data": "没有资料库,尝试创建一个。", + "loading": "{{common.loading}}", + "last-scanned-title": "上次扫描:", + "shared-folders-title": "共享文件夹:", + "type-title": "类型:", + "scan-library": "扫描资料库", + "delete-library": "删除资料库", + "delete-library-by-name": "删除{{name}}", + "edit-library": "编辑", + "edit-library-by-name": "删除{{name}}" }, "manage-media-settings": { - "encode-as-description-part-1": "", - "encode-as-description-part-2": "", - "encode-as-description-part-3": "", - "encode-as-warning": "", - "media-warning": "", - "encode-as-label": "", - "encode-as-tooltip": "", - "bookmark-dir-label": "", - "bookmark-dir-tooltip": "", - "change": "", - "reset-to-default": "", - "reset": "", - "save": "", - "media-issue-title": "", - "scrobble-issue-title": "" + "encode-as-description-part-1": "WebP/AVIF 可以极大减少文件的空间需求。但WebP/AVIF 并不支持所有浏览器。要了解这些设置是否符合您的要求,请访问 ", + "encode-as-description-part-2": "我可以使用WebP吗?", + "encode-as-description-part-3": "我可以使用 AVIF 吗?", + "encode-as-warning": "一旦转换为WebP/AVIF,您就无法再转换回PNG格式。您需要刷新图书馆封面以重新生成所有封面。书签和网站图标无法进行转换。", + "media-warning": "您必须在“任务”选项卡中触发媒体转换任务。", + "encode-as-label": "媒体另存为", + "encode-as-tooltip": "Kavita管理的所有媒体(封面,书签,图标)都将采用此类型编码。", + "bookmark-dir-label": "书签目录", + "bookmark-dir-tooltip": "书签的存储路径。书签是资源文件,占用空间较大。请选择一个具有足够空间的存储位置。被管理目录中的其他文件将被删除。如果使用Docker,请挂载并使用一个额外的卷。", + "change": "更改", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "media-issue-title": "媒体问题", + "scrobble-issue-title": "跟踪记录问题", + "cover-image-size-tooltip": "生成封面图片的尺寸。注意:任何大于默认值的图片都会延长页面加载时间。", + "cover-image-size-label": "封面图片尺寸" }, "manage-scrobble-errors": { - "description": "", - "filter-label": "", - "clear-errors": "", - "series-header": "", - "created-header": "", - "comment-header": "", - "edit-header": "", - "edit-item-alt": "" + "description": "此表格记载跟踪记录时发现的问题。此列表不受管控。您可以随时清空列表,并在下一次跟踪记录上传后进行查看。如果存在一个未知系列,您最好更正系列名称或本地化系列名称,或者为跟踪记录的服务提供商添加一个网页链接。", + "filter-label": "筛选", + "clear-errors": "清除错误", + "series-header": "系列", + "created-header": "创建", + "comment-header": "评论", + "edit-header": "编辑", + "edit-item-alt": "编辑{{seriesName}}" }, "default-date-pipe": { - "never": "" + "never": "从不" }, "manage-settings": { - "notice": "", - "restart-required": "", - "base-url-label": "", - "base-url-tooltip": "", - "ip-address-label": "", - "ip-address-tooltip": "", - "port-label": "", - "port-tooltip": "", - "backup-label": "", - "backup-tooltip": "", - "log-label": "", - "log-tooltip": "", - "logging-level-label": "", - "logging-level-tooltip": "", - "cache-size-label": "", - "cache-size-tooltip": "", - "on-deck-last-progress-label": "", - "on-deck-last-progress-tooltip": "", - "on-deck-last-chapter-add-label": "", - "on-deck-last-chapter-add-tooltip": "", - "allow-stats-label": "", - "allow-stats-tooltip-part-1": "", - "allow-stats-tooltip-part-2": "", - "send-data": "", - "opds-label": "", - "opds-tooltip": "", - "enable-opds": "", - "folder-watching-label": "", - "folder-watching-tooltip": "", - "enable-folder-watching": "", - "reset-to-default": "", - "reset": "", - "save": "", - "cache-size-validation": "", - "field-required": "", - "max-logs-validation": "", - "min-logs-validation": "", - "min-days-validation": "", - "min-cache-validation": "", - "max-backup-validation": "", - "min-backup-validation": "", - "ip-address-validation": "", - "base-url-validation": "" + "notice": "注意:", + "restart-required": "更改端口、网址、缓存大小或者IP后,需要重启Kavita才能生效。", + "base-url-label": "基本URL", + "base-url-tooltip": "如果您想在基本URL上托管Kavita,例如yourdomain.com/kavita,请使用此选项。在使用非root用户的Docker上不支持该功能。", + "ip-address-label": "IP地址", + "ip-address-tooltip": "服务器监听的IP地址列表,以逗号分隔。如果Kavita在Docker中运行,则此选项无法更改。需要重启服务才能生效。", + "port-label": "端口", + "port-tooltip": "服务器监听的端口号。如果Kavita在Docker中运行,则此选项无法更改。需要重启服务才能生效。", + "backup-label": "备份天数", + "backup-tooltip": "要保留的备份数量。默认值 30,最小值1,最大值 30。", + "log-label": "日志天数", + "log-tooltip": "要保留的日志数量。默认值 30,最小值 1,最大值 30。", + "logging-level-label": "日志等级", + "logging-level-tooltip": "使用调试有助于发现问题。调试会占用大量磁盘空间。", + "cache-size-label": "缓存大小", + "cache-size-tooltip": "重度API缓存允许的内存容量。默认值75MB。", + "on-deck-last-progress-label": "“最近阅读”最后进度(天数)", + "on-deck-last-progress-tooltip": "自上次阅读进度加入最近阅读到从最近阅读移除的天数。", + "on-deck-last-chapter-add-label": "最近阅读上一次章节加入(天数)", + "on-deck-last-chapter-add-tooltip": "上一次章节内容加入至最近阅读的天数。", + "allow-stats-label": "允许匿名收集使用数据", + "allow-stats-tooltip-part-1": "采用匿名方式将使用数据发送到Kavita服务器。这些数据包括使用的某些功能、文件数量、操作系统版本、Kavita版本、CPU和内存。我们将使用这些信息来确定功能开发、错误修复和性能调整的优先级。需要重新启动才能生效。请参阅 ", + "allow-stats-tooltip-part-2": "了解收集的内容。", + "send-data": "发送数据", + "opds-label": "OPDS", + "opds-tooltip": "OPDS支持所有用户使用OPDS功能从服务器阅读和下载内容。", + "enable-opds": "启用OPDS", + "folder-watching-label": "监控文件夹", + "folder-watching-tooltip": "允许Kavita监控资料库文件夹以检测更改,并在文件夹发生更改时激活扫描。这样可以在没有手动激活扫描或等待每晚扫描计划任务的情况下更新内容。", + "enable-folder-watching": "启用监控文件夹", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "cache-size-validation": "您必须至少有50MB。", + "field-required": "{{validation.field-required}}", + "max-logs-validation": "您不能拥有超过{{num}}条日志", + "min-logs-validation": "您必须至少有 1 个日志", + "min-days-validation": "必须至少为 1 天", + "min-cache-validation": "必须为 50 MB。", + "max-backup-validation": "您不能拥有超过{{num}}个备份", + "min-backup-validation": "您必须至少有 1 个备份", + "ip-address-validation": "IP地址只能包含有效的IPv4或IPv6地址", + "base-url-validation": "基本URL必须以/开头和结尾" }, "manage-system": { - "title": "", - "version-title": "", - "installId-title": "", - "more-info-title": "", - "home-page-title": "", - "wiki-title": "", - "discord-title": "", - "donations-title": "", - "source-title": "", - "feature-request-title": "" + "title": "关于系统", + "version-title": "版本", + "installId-title": "安装ID", + "more-info-title": "更多信息", + "home-page-title": "主页:", + "wiki-title": "wiki:", + "discord-title": "discord:", + "donations-title": "捐赠:", + "source-title": "源代码:", + "feature-request-title": "功能请求" }, "manage-tasks-settings": { - "title": "", - "library-scan-label": "", - "library-scan-tooltip": "", - "library-database-backup-label": "", - "library-database-backup-tooltip": "", - "adhoc-tasks-title": "", - "job-title-header": "", - "description-header": "", - "action-header": "", - "reset-to-default": "", - "reset": "", - "save": "", - "recurring-tasks-title": "", - "last-executed-header": "", - "cron-header": "", - "convert-media-task": "", - "convert-media-task-desc": "", - "convert-media-success": "", - "bust-cache-task": "", - "bust-cache-task-desc": "", - "bust-cache-task-success": "", - "clear-reading-cache-task": "", - "clear-reading-cache-task-desc": "", - "clear-reading-cache-task-success": "", - "clean-up-want-to-read-task": "", - "clean-up-want-to-read-task-desc": "", - "clean-up-want-to-read-task-success": "", - "backup-database-task": "", - "backup-database-task-desc": "", - "backup-database-task-success": "", - "download-logs-task": "", - "download-logs-task-desc": "", - "analyze-files-task": "", - "analyze-files-task-desc": "", - "analyze-files-task-success": "", - "check-for-updates-task": "", - "check-for-updates-task-desc": "" + "title": "周期性任务", + "library-scan-label": "扫描资料库", + "library-scan-tooltip": "Kavita扫描资料库并刷新元数据的频率。", + "library-database-backup-label": "备份资料库的数据库", + "library-database-backup-tooltip": "Kavita备份数据库的频率。", + "adhoc-tasks-title": "临时任务", + "job-title-header": "任务名称", + "description-header": "描述", + "action-header": "操作", + "reset-to-default": "{{common.reset-to-default}}", + "reset": "{{common.reset}}", + "save": "{{common.save}}", + "recurring-tasks-title": "{{title}}", + "last-executed-header": "上次执行", + "cron-header": "计划任务", + "convert-media-task": "将媒体转换为目标编码格式", + "convert-media-task-desc": "运行一个长时间运行的任务,将所有由Kavita管理的媒体转换为目标编码格式。这个过程较慢(特别是在ARM设备上)。", + "convert-media-success": "媒体转换为目标编码已进入队列", + "bust-cache-task": "清理缓存", + "bust-cache-task-desc": "清除Kavita+缓存 - 仅在调试错误匹配时使用。", + "bust-cache-task-success": "Kavita+缓存已清除", + "clear-reading-cache-task": "清除阅读缓存", + "clear-reading-cache-task-desc": "清除阅读缓存的文件。当您刚刚更新了您在过去24小时内曾经阅读过的文件时,这将非常有用。", + "clear-reading-cache-task-success": "缓存已被清除", + "clean-up-want-to-read-task": "清理准备阅读列表", + "clean-up-want-to-read-task-desc": "移除用户已完全阅读并且在“准备阅读”列表中且发布状态为“已完成”的系列。每 24 小时运行一次。", + "clean-up-want-to-read-task-success": "准备阅读列表已清理完成", + "backup-database-task": "备份数据库", + "backup-database-task-desc": "对数据库、书签、主题、手动上传的封面以及配置文件进行备份。", + "backup-database-task-success": "已将备份数据库的任务加入队列", + "download-logs-task": "下载日志文件", + "download-logs-task-desc": "将所有日志文件编译成一个 zip 文件并下载。", + "analyze-files-task": "分析文件", + "analyze-files-task-desc": "运行一个长时间运行的任务,用于分析文件以生成扩展名和大小。这只需要在v0.7版本中运行一次。如果您是在v0.7之后安装的,则不需要运行此任务。", + "analyze-files-task-success": "文件分析已进入队列", + "check-for-updates-task": "检查更新", + "check-for-updates-task-desc": "查看您的版本之前是否有任何稳定版本发布。" }, "manage-users": { - "title": "", - "invite": "", - "you-alt": "", - "pending-title": "", - "delete-user-tooltip": "", - "delete-user-alt": "", - "edit-user-tooltip": "", - "edit-user-alt": "", - "resend-invite-tooltip": "", - "resend-invite-alt": "", - "setup-user-tooltip": "", - "setup-user-alt": "", - "change-password-tooltip": "", - "change-password-alt": "", - "resend": "", - "setup": "", - "last-active-title": "", - "roles-title": "", - "none": "", - "never": "", - "online-now-tooltip": "", - "sharing-title": "", - "no-data": "", - "loading": "" + "title": "活跃用户", + "invite": "邀请", + "you-alt": "(你)", + "pending-title": "等待激活", + "delete-user-tooltip": "删除用户", + "delete-user-alt": "删除用户 {{user}}", + "edit-user-tooltip": "编辑", + "edit-user-alt": "编辑用户 {{user}}", + "resend-invite-tooltip": "重新发送邀请", + "resend-invite-alt": "重新发送邀请 {{user}}", + "setup-user-tooltip": "设置用户", + "setup-user-alt": "设置用户 {{user}}", + "change-password-tooltip": "更改密码", + "change-password-alt": "更改密码 {{user}}", + "resend": "重新发送", + "setup": "设置", + "last-active-title": "上次活动:", + "roles-title": "角色:", + "none": "没有", + "never": "从不", + "online-now-tooltip": "在线", + "sharing-title": "共享:", + "no-data": "没有其他用户。", + "loading": "{{common.loading}}" }, "edit-collection-tags": { - "title": "", - "required-field": "", - "save": "", - "close": "", - "cancel": "", - "general-tab": "", - "cover-image-tab": "", - "series-tab": "", - "name-label": "", - "name-validation": "", - "promote-label": "", - "promote-tooltip": "", - "summary-label": "", - "series-title": "", - "deselect-all": "", - "select-all": "" + "title": "编辑{{collectionName}} 收藏", + "required-field": "{{validation.required-field}}", + "save": "{{common.save}}", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "general-tab": "常规", + "cover-image-tab": "封面图片", + "series-tab": "系列", + "name-label": "名称", + "name-validation": "名称必须唯一", + "promote-label": "推广", + "promote-tooltip": "推广意味着该标签能在整个服务器范围内可见,不仅仅限于管理员用户。拥有该标签的所有系列仍然会受到用户访问限制。", + "summary-label": "内容简介", + "series-title": "应用系列", + "deselect-all": "{{common.deselect-all}}", + "select-all": "{{common.select-all}}" }, "library-detail": { - "library-tab": "", - "recommended-tab": "" + "library-tab": "资料库", + "recommended-tab": "推荐" }, "library-recommended": { - "no-data": "", - "more-in-genre": "", - "rediscover": "", - "highly-rated": "", - "quick-catchups": "", - "quick-reads": "", - "on-deck": "" + "no-data": "没有要显示的内容。请给资料库添加一些元数据,阅读一些内容或对某些内容进行评价。也可能是资料库的推荐功能被关闭了。", + "more-in-genre": "{{genre}}类别的更多内容", + "rediscover": "重温", + "highly-rated": "最高评价", + "quick-catchups": "快速回顾", + "quick-reads": "快速阅读", + "on-deck": "{{dashboard.on-deck-title}}" }, "admin-dashboard": { - "title": "", - "general-tab": "", - "users-tab": "", - "libraries-tab": "", - "media-tab": "", - "logs-tab": "", - "email-tab": "", - "tasks-tab": "", - "statistics-tab": "", - "system-tab": "", - "kavita+-tab": "", - "kavita+-desc-part-1": "", - "kavita+-desc-part-2": "", - "kavita+-desc-part-3": "" + "title": "管理员面板", + "general-tab": "常规", + "users-tab": "用户", + "libraries-tab": "资料库", + "media-tab": "媒体", + "logs-tab": "日志", + "email-tab": "电子邮件", + "tasks-tab": "任务", + "statistics-tab": "统计数据", + "system-tab": "系统", + "kavita+-tab": "Kavita+", + "kavita+-desc-part-1": "Kavita+是一项高级订阅服务,可为Kavita上的所有用户解锁功能。购买订阅解锁 ", + "kavita+-desc-part-2": "高级会员福利", + "kavita+-desc-part-3": "!" }, "collection-detail": { - "no-data": "", - "no-data-filtered": "", - "title-alt": "" + "no-data": "暂无项目。请尝试添加一个系列。", + "no-data-filtered": "没有项目与当前筛选器匹配。", + "title-alt": "Kavita - {{collectionName}}收藏集" }, "all-collections": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "收藏", + "item-count": "{{common.item-count}}", + "no-data": "没有收藏。", + "create-one-part-1": "尝试创建", + "create-one-part-2": "一个" }, "carousel-reel": { - "prev-items": "", - "next-items": "" + "prev-items": "上一项", + "next-items": "下一项" }, "draggable-ordered-list": { - "instructions-alt": "", - "reorder-label": "", - "remove-item-alt": "" + "instructions-alt": "当您在重新排序输入框中输入一个数字时,该项将被插入到该位置,并且所有其他项的顺序将得到更新。", + "reorder-label": "重新排序", + "remove-item-alt": "移除条目" }, "reading-lists": { - "title": "", - "item-count": "", - "no-data": "", - "create-one-part-1": "", - "create-one-part-2": "" + "title": "阅读清单", + "item-count": "{{common.item-count}}", + "no-data": "没有阅读清单。", + "create-one-part-1": "尝试创建", + "create-one-part-2": "一个" }, "reading-list-item": { - "remove": "", - "read": "" + "remove": "{{common.remove}}", + "read": "{{common.read}}" }, "reading-list-detail": { - "item-count": "", - "page-settings-title": "", - "remove-read": "", - "order-numbers-label": "", - "continue": "", - "read": "", - "read-options-alt": "", - "incognito-alt": "", - "no-data": "" + "item-count": "{{common.item-count}}", + "page-settings-title": "页面设置", + "remove-read": "移除已阅读", + "order-numbers-label": "排序编号", + "continue": "继续", + "read": "{{common.read}}", + "read-options-alt": "阅读选项", + "incognito-alt": "(隐身)", + "no-data": "未添加任何内容", + "characters-title": "{{series-metadata-detail.characters-title}}" }, "events-widget": { - "title-alt": "", - "dismiss-all": "", - "update-available": "", - "downloading-item": "", - "more-info": "", - "close": "", - "users-online-count": "", - "active-events-title": "", - "no-data": "" + "title-alt": "活动", + "dismiss-all": "全部关闭", + "update-available": "更新可用", + "downloading-item": "正在下载 {{item}}", + "more-info": "点击查看更多信息", + "close": "{{common.close}}", + "users-online-count": "{{num}}用户在线", + "active-events-title": "活动事件:", + "no-data": "什么都没有发生" }, "shortcuts-modal": { - "title": "", - "close": "", - "prev-page": "", - "next-page": "", - "go-to": "", - "bookmark": "", - "double-click": "", - "close-reader": "", - "toggle-menu": "" + "title": "热键", + "close": "{{common.close}}", + "prev-page": "移至上一页", + "next-page": "移至下一页", + "go-to": "“打开转到页面”对话框", + "bookmark": "为当前页面添加书签", + "double-click": "双击", + "close-reader": "关闭阅读器", + "toggle-menu": "切换菜单" }, "grouped-typeahead": { - "files": "", - "chapters": "", - "people": "", - "tags": "", - "genres": "", - "libraries": "", - "reading-lists": "", - "collections": "", - "close": "", - "loading": "" + "files": "文件", + "chapters": "章节", + "people": "创作人员", + "tags": "标签", + "genres": "类别", + "libraries": "资料库", + "reading-lists": "阅读清单", + "collections": "收藏", + "close": "{{common.close}}", + "loading": "{{common.loading}}" }, "nav-header": { - "skip-alt": "", - "search-series-alt": "", - "search-alt": "", - "promoted": "", - "no-data": "", - "scroll-to-top-alt": "", - "server-settings": "", - "settings": "", - "help": "", - "announcements": "", - "logout": "" + "skip-alt": "跳转至主要内容", + "search-series-alt": "搜索系列", + "search-alt": "搜索…", + "promoted": "(推广)", + "no-data": "无结果", + "scroll-to-top-alt": "滚动到顶部", + "server-settings": "服务器设置", + "settings": "设置", + "help": "帮助", + "announcements": "公告", + "logout": "注销" }, "add-to-list-modal": { - "title": "", - "close": "", - "filter-label": "", - "promoted-alt": "", - "no-data": "", - "loading": "", - "reading-list-label": "", - "create": "" + "title": "添加到阅读清单", + "close": "{{common.close}}", + "filter-label": "筛选", + "promoted-alt": "推广", + "no-data": "尚未创建任何清单", + "loading": "{{common.loading}}", + "reading-list-label": "阅读清单", + "create": "{{common.create}}" }, "edit-reading-list-modal": { - "title": "", - "general-tab": "", - "cover-image-tab": "", - "close": "", - "save": "", - "year-validation": "", - "month-validation": "", - "name-unique-validation": "", - "required-field": "", - "summary-label": "", - "year-label": "", - "month-label": "", - "ending-title": "", - "starting-title": "", - "promote-label": "", - "promote-tooltip": "" + "title": "编辑阅读清单: {{name}}", + "general-tab": "常规", + "cover-image-tab": "封面图片", + "close": "{{common.close}}", + "save": "{{common.save}}", + "year-validation": "必须大于 1000、0 或空白", + "month-validation": "必须介于 1 和 12 之间或为空", + "name-unique-validation": "名称必须唯一", + "required-field": "{{validation.required-field}}", + "summary-label": "内容简介", + "year-label": "年", + "month-label": "月", + "ending-title": "结束", + "starting-title": "开始", + "promote-label": "推广", + "promote-tooltip": "推广意味着该标签能在整个服务器范围内可见,不仅仅限于管理员用户。拥有该标签的所有系列仍然会受到用户访问限制。" }, "import-cbl-modal": { - "close": "", - "title": "", - "import-description": "", - "validate-description": "", - "validate-warning": "", - "validate-no-issue": "", - "validate-no-issue-description": "", - "dry-run-description": "", - "prev": "", - "import": "", - "restart": "", - "next": "", - "import-step": "", - "validate-cbl-step": "", - "dry-run-step": "", - "final-import-step": "" + "close": "{{common.close}}", + "title": "CBL导入", + "import-description": "要开始,请导入一个.cbl文件。Kavita将在导入之前执行多个检查。某些步骤可能会因文件问题而阻止继续进行。", + "validate-description": "已验证所有文件,以查看列表中是否有任何操作需要执行。任何失败的列表将不会进入下一步。请修复CBL文件并重试。", + "validate-warning": "CBL存在问题,这会阻止导入。请纠正这些问题,然后重试。", + "validate-no-issue": "看起来不错", + "validate-no-issue-description": "未发现与CBL相关的问题,请点击“下一步”。", + "dry-run-description": "这是一次模拟运行,显示了如果您点击“下一步”并执行导入操作将会发生什么。所有失败的操作将不会被导入。", + "prev": "上一页", + "import": "导入", + "restart": "重新开始", + "next": "下一项", + "import-step": "导入CBL文件", + "validate-cbl-step": "验证CBL文件", + "dry-run-step": "模拟运行", + "final-import-step": "最后一步" }, "pdf-reader": { - "loading-message": "", - "incognito-mode": "", - "light-theme-alt": "", - "dark-theme-alt": "", - "close-reader-alt": "" - }, - "infinite-reader": { - "continuous-reading-prev-chapter-alt": "", - "continuous-reading-prev-chapter": "", - "continuous-reading-next-chapter-alt": "", - "continuous-reading-next-chapter": "" + "loading-message": "加载中...PDF文件可能需要比预期更长的时间", + "incognito-mode": "隐身模式", + "light-theme-alt": "明亮主题", + "dark-theme-alt": "深色主题", + "close-reader-alt": "关闭阅读器" }, "manga-reader": { - "back": "", - "save-globally": "", - "incognito-alt": "", - "incognito-title": "", - "shortcuts-menu-alt": "", - "prev-page-tooltip": "", - "next-page-tooltip": "", - "prev-chapter-tooltip": "", - "next-chapter-tooltip": "", - "first-page-tooltip": "", - "last-page-tooltip": "", - "left-to-right-alt": "", - "right-to-left-alt": "", - "reading-direction-tooltip": "", - "reading-mode-tooltip": "", - "collapse": "", - "fullscreen": "", - "settings-tooltip": "", - "image-splitting-label": "", - "image-scaling-label": "", - "height": "", - "width": "", - "original": "", - "auto-close-menu-label": "", - "swipe-enabled-label": "", - "enable-comic-book-label": "", - "brightness-label": "", - "first-time-reading-manga": "", - "layout-mode-switched": "", - "no-next-chapter": "", - "no-prev-chapter": "", - "user-preferences-updated": "" + "back": "返回", + "save-globally": "全局保存", + "incognito-alt": "隐身模式已开启。切换以关闭。", + "incognito-title": "隐身模式:", + "shortcuts-menu-alt": "键盘快捷键模式", + "prev-page-tooltip": "上一页", + "next-page-tooltip": "下一页", + "prev-chapter-tooltip": "上一章/卷", + "next-chapter-tooltip": "下一章/卷", + "first-page-tooltip": "首页", + "last-page-tooltip": "尾页", + "left-to-right-alt": "从左到右", + "right-to-left-alt": "从右到左", + "reading-direction-tooltip": "阅读方向: ", + "reading-mode-tooltip": "阅读模式", + "collapse": "折叠", + "fullscreen": "全屏", + "settings-tooltip": "设置", + "image-splitting-label": "图像分割", + "image-scaling-label": "图像缩放", + "height": "高度", + "width": "宽度", + "original": "原始尺寸", + "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "swipe-enabled-label": "启用滑动手势", + "enable-comic-book-label": "模拟漫画书", + "brightness-label": "亮度", + "first-time-reading-manga": "您随时可以点击图片打开菜单。配置不同的设置,或者通过点击进度条跳转到某一页。点击图片的两侧可以翻到下一页/上一页。", + "layout-mode-switched": "由于空间不足以呈现双页布局,布局模式已切换为单页", + "no-next-chapter": "没有下一章节", + "no-prev-chapter": "没有上一章节", + "user-preferences-updated": "用户偏好已更新", + "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}" }, "metadata-filter": { - "filter-title": "", - "format-label": "", - "format-tooltip": "", - "libraries-label": "", - "collections-label": "", - "genres-label": "", - "tags-label": "", - "cover-artist-label": "", - "writer-label": "", - "publisher-label": "", - "penciller-label": "", - "letterer-label": "", - "inker-label": "", - "editor-label": "", - "colorist-label": "", - "character-label": "", - "translator-label": "", - "read-progress-label": "", - "unread": "", - "read": "", - "in-progress": "", - "rating-label": "", - "age-rating-label": "", - "language-label": "", - "publication-status-label": "", - "series-name-label": "", - "series-name-tooltip": "", - "release-label": "", - "min": "", - "max": "", - "sort-by-label": "", - "ascending-alt": "", - "descending-alt": "", - "reset": "", - "apply": "" + "filter-title": "筛选", + "format-label": "格式", + "libraries-label": "资料库", + "collections-label": "收藏", + "genres-label": "类别", + "tags-label": "标签", + "cover-artist-label": "封面设计", + "writer-label": "作者", + "publisher-label": "出版社", + "penciller-label": "线稿师", + "letterer-label": "排版", + "inker-label": "上墨师", + "editor-label": "编辑", + "colorist-label": "上色师", + "character-label": "角色", + "translator-label": "译者", + "read-progress-label": "阅读进度", + "unread": "未读", + "read": "已读", + "in-progress": "阅读中", + "rating-label": "评分", + "age-rating-label": "年龄分级", + "language-label": "语言", + "publication-status-label": "出版状态", + "series-name-label": "系列名称", + "series-name-tooltip": "系列名称将与名称、排序名称或本地化名称进行筛选", + "release-label": "发行", + "min": "最小值", + "max": "最大值", + "sort-by-label": "排序方式", + "ascending-alt": "升序", + "descending-alt": "降序", + "reset": "{{common.reset}}", + "apply": "{{common.apply}}", + "limit-label": "只显示" }, "sort-field-pipe": { - "sort-name": "", - "created": "", - "last-modified": "", - "last-chapter-added": "", - "time-to-read": "", - "release-year": "" + "sort-name": "排序名称", + "created": "创建时间", + "last-modified": "上次修改时间", + "last-chapter-added": "添加时间", + "time-to-read": "阅读时间", + "release-year": "发行年份" }, "edit-series-modal": { - "title": "", - "general-tab": "", - "metadata-tab": "", - "people-tab": "", - "web-links-tab": "", - "cover-image-tab": "", - "related-tab": "", - "info-tab": "", - "collections-label": "", - "genres-label": "", - "tags-label": "", - "cover-artist-label": "", - "writer-label": "", - "publisher-label": "", - "penciller-label": "", - "letterer-label": "", - "inker-label": "", - "editor-label": "", - "colorist-label": "", - "character-label": "", - "translator-label": "", - "language-label": "", - "age-rating-label": "", - "publication-status-label": "", - "required-field": "", - "close": "", - "name-label": "", - "sort-name-label": "", - "localized-name-label": "", - "summary-label": "", - "release-year-label": "", - "web-link-description": "", - "web-link-label": "", - "add-link-alt": "", - "remove-link-alt": "", - "cover-image-description": "", - "save": "", - "field-locked-alt": "", - "info-title": "", - "library-title": "", - "format-title": "", - "created-title": "", - "last-read-title": "", - "last-added-title": "", - "last-scanned-title": "", - "folder-path-title": "", - "publication-status-title": "", - "total-pages-title": "", - "total-items-title": "", - "max-items-title": "", - "size-title": "", - "loading": "", - "added-title": "", - "last-modified-title": "", - "view-files": "", - "pages-title": "", - "chapter-title": "", - "volume-num": "", - "highest-count-tooltip": "", - "max-issue-tooltip": "" + "title": "{{seriesName}} 详细信息", + "general-tab": "常规", + "metadata-tab": "元数据", + "people-tab": "创作人员", + "web-links-tab": "网页链接", + "cover-image-tab": "封面图片", + "related-tab": "相关", + "info-tab": "信息", + "collections-label": "收藏", + "genres-label": "类别", + "tags-label": "标签", + "cover-artist-label": "封面设计", + "writer-label": "作者", + "publisher-label": "出版社", + "penciller-label": "线稿师", + "letterer-label": "排版", + "inker-label": "上墨师", + "editor-label": "编辑", + "colorist-label": "上色师", + "character-label": "角色", + "translator-label": "译者", + "language-label": "语言", + "age-rating-label": "年龄分级", + "publication-status-label": "出版状态", + "required-field": "{{validation.required-field}}", + "close": "{{common.close}}", + "name-label": "名称", + "sort-name-label": "排序名称", + "localized-name-label": "本地化名称", + "summary-label": "内容简介", + "release-year-label": "发行年份", + "web-link-description": "您可以在这里添加多个不同的外部网站链接。", + "web-link-label": "网页链接", + "add-link-alt": "添加链接", + "remove-link-alt": "移除链接", + "cover-image-description": "选择一张新封面图片并上传。点击保存上传图片并覆盖原有封面。", + "save": "{{common.save}}", + "field-locked-alt": "字段已锁定", + "info-title": "信息", + "library-title": "资料库:", + "format-title": "格式:", + "created-title": "创建时间:", + "last-read-title": "上次阅读:", + "last-added-title": "最后加入条目:", + "last-scanned-title": "上次扫描:", + "folder-path-title": "文件路径:", + "publication-status-title": "出版状态:", + "total-pages-title": "总页数:", + "total-items-title": "总条目:", + "max-items-title": "最大条目:", + "size-title": "文件大小:", + "loading": "{{common.loading}}", + "added-title": "添加时间:", + "last-modified-title": "上次修改时间:", + "view-files": "查看文件", + "pages-title": "页数:", + "chapter-title": "章节:", + "volume-num": "{{common.volume-num}}", + "highest-count-tooltip": "在系列的所有ComicInfo中找到的最高计数", + "max-issue-tooltip": "从系列中的所有ComicInfo中获取最大话数或卷数字段" }, "day-breakdown": { - "title": "", - "x-axis-label": "", - "y-axis-label": "" + "title": "每日统计分析", + "x-axis-label": "按天进行分类统计", + "y-axis-label": "阅读事件" }, "file-breakdown-stats": { - "format-title": "", - "format-tooltip": "", - "visualisation-label": "", - "data-table-label": "", - "extension-header": "", - "format-header": "", - "total-size-header": "", - "total-files-header": "", - "not-classified": "", - "total-file-size-title": "" + "format-title": "格式", + "format-tooltip": "未分类表示Kavita尚未扫描某些文件。这通常发生在v0.7之前已经存在的旧文件上。您可能需要在资料库设置中执行强制扫描。", + "visualisation-label": "可视化", + "data-table-label": "数据表格", + "extension-header": "扩展名", + "format-header": "格式", + "total-size-header": "总大小", + "total-files-header": "文件总数", + "not-classified": "未分类", + "total-file-size-title": "总文件大小:" }, "reading-activity": { - "title": "", - "legend-label": "", - "x-axis-label": "", - "y-axis-label": "", - "no-data": "", - "time-frame-label": "" + "title": "阅读活动", + "legend-label": "格式", + "x-axis-label": "时间", + "y-axis-label": "阅读时长", + "no-data": "没有阅读进度", + "time-frame-label": "时间范围", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}" }, "manga-format-stats": { - "title": "", - "visualisation-label": "", - "data-table-label": "", - "format-header": "", - "count-header": "" + "title": "格式", + "visualisation-label": "可视化", + "data-table-label": "数据表格", + "format-header": "格式", + "count-header": "数量" }, "publication-status-stats": { - "title": "", - "visualisation-label": "", - "data-table-label": "", - "year-header": "", - "count-header": "" + "title": "出版状态", + "visualisation-label": "可视化", + "data-table-label": "数据表格", + "year-header": "年份", + "count-header": "数量" }, "server-stats": { - "total-series-label": "", - "total-series-tooltip": "", - "total-volumes-label": "", - "total-volumes-tooltip": "", - "total-files-label": "", - "total-files-tooltip": "", - "total-size-label": "", - "total-genres-label": "", - "total-genres-tooltip": "", - "total-tags-label": "", - "total-tags-tooltip": "", - "total-people-label": "", - "total-people-tooltip": "", - "total-read-time-label": "", - "total-read-time-tooltip": "", - "series": "", - "reads": "", - "release-years-title": "", - "most-active-users-title": "", - "popular-libraries-title": "", - "popular-series-title": "", - "recently-read-title": "", - "genre-count": "", - "tag-count": "", - "people-count": "", - "tags": "", - "people": "", - "genres": "" + "total-series-label": "系列总数", + "total-series-tooltip": "系列总数: {{count}}", + "total-volumes-label": "总卷数", + "total-volumes-tooltip": "总卷数: {{count}}", + "total-files-label": "文件总数", + "total-files-tooltip": "文件总数: {{count}}", + "total-size-label": "总大小", + "total-genres-label": "类别总数", + "total-genres-tooltip": "类别总数: {{count}}", + "total-tags-label": "标签总数", + "total-tags-tooltip": "标签总数: {{count}}", + "total-people-label": "创作人员总数", + "total-people-tooltip": "创作人员总数: {{count}}", + "total-read-time-label": "总阅读时长", + "total-read-time-tooltip": "总阅读时长: {{count}}", + "series": "系列", + "reads": "次阅读", + "release-years-title": "发行年份计数", + "most-active-users-title": "活跃用户排行榜", + "popular-libraries-title": "热门资料库", + "popular-series-title": "热门系列", + "recently-read-title": "最近阅读", + "genre-count": "{{num}} 类别", + "tag-count": "{{num}} 标签", + "people-count": "{{num}} 人", + "tags": "标签", + "people": "创作人员", + "genres": "类别" }, "errors": { - "series-doesnt-exist": "", - "collection-invalid-access": "", - "unknown-crit": "", - "user-not-auth": "", - "error-code": "", - "download": "", - "not-found": "", - "generic": "", - "rejected-cover-upload": "", - "invalid-confirmation-url": "", - "invalid-confirmation-email": "", - "invalid-password-reset-url": "" + "series-doesnt-exist": "该系列已不存在", + "collection-invalid-access": "您无权访问该标签所属的任何资料库,或者该收藏无效", + "unknown-crit": "发生了未知的严重错误", + "user-not-auth": "用户未通过身份验证", + "error-code": "{{num}}错误", + "download": "下载此文件时出现问题,或者您没有权限访问该文件", + "not-found": "该URL不存在", + "generic": "发生了意外的错误", + "rejected-cover-upload": "由于服务器拒绝请求,无法获取图像。请下载并从文件中上传。", + "invalid-confirmation-url": "无效的确认URL", + "invalid-confirmation-email": "无效的确认电子邮件", + "invalid-password-reset-url": "无效的重置密码URL" }, "toasts": { - "regen-cover": "", - "no-pages": "", - "download-in-progress": "", - "scan-queued": "", - "server-settings-updated": "", - "reset-ip-address": "", - "reset-base-url": "", - "unauthorized-1": "", - "unauthorized-2": "", - "no-updates": "", - "confirm-delete-user": "", - "user-deleted": "", - "email-sent-to-user": "", - "click-email-link": "", - "series-added-to-collection": "", - "no-series-collection-warning": "", - "collection-updated": "", - "reading-list-deleted": "", - "reading-list-updated": "", - "confirm-delete-reading-list": "", - "item-removed": "", - "nothing-to-remove": "", - "series-added-to-reading-list": "", - "volumes-added-to-reading-list": "", - "chapter-added-to-reading-list": "", - "multiple-added-to-reading-list": "", - "select-files-warning": "", - "reading-list-imported": "", - "incognito-off": "", - "email-service-reset": "", - "email-service-reachable": "", - "email-service-unresponsive": "", - "refresh-covers-queued": "", - "library-file-analysis-queued": "", - "entity-read": "", - "entity-unread": "", - "mark-read": "", - "mark-unread": "", - "series-removed-want-to-read": "", - "series-deleted": "", - "file-send-to": "", - "theme-missing": "", - "email-sent": "", - "k+-license-saved": "", - "k+-unlocked": "", - "k+-error": "", - "k+-delete-key": "", - "library-deleted": "", - "copied-to-clipboard": "", - "book-settings-info": "", - "no-next-chapter": "", - "no-prev-chapter": "", - "load-next-chapter": "", - "load-prev-chapter": "", - "account-registration-complete": "", - "account-migration-complete": "", - "password-reset": "", - "password-updated": "", - "forced-scan-queued": "", - "library-created": "", - "anilist-token-updated": "", - "age-restriction-updated": "", - "email-sent-to-no-existing": "", - "email-sent-to": "", - "change-email-private": "", - "device-updated": "", - "device-created": "", - "confirm-regen-covers": "", - "alert-long-running": "", - "confirm-delete-multiple-series": "", - "confirm-delete-series": "", - "alert-bad-theme": "", - "confirm-library-delete": "", - "confirm-library-type-change": "", - "confirm-download-size": "" + "regen-cover": "已加入队列的任务以重新生成封面图像", + "no-pages": "没有任何页面。Kavita无法读取此存档文件。", + "download-in-progress": "下载正在进行中,请稍候。", + "scan-queued": "{{name}}进入队列扫描", + "server-settings-updated": "服务器设置已更新", + "reset-ip-address": "IP地址已重置", + "reset-base-url": "基本URL已重置", + "unauthorized-1": "您没有权限查看此页面。", + "unauthorized-2": "未经授权", + "no-updates": "暂无可用更新", + "confirm-delete-user": "您确定要删除此用户吗?", + "user-deleted": "{{user}}已被删除", + "email-sent-to-user": "已向{{user}}发送电子邮件", + "click-email-link": "请点击此链接确认您的电子邮件。您必须确认后才能登录。", + "series-added-to-collection": "系列已添加到{{collectionName}}收藏集", + "no-series-collection-warning": "警告!未选择任何系列,保存将删除该收藏集。您确定要继续吗?", + "collection-updated": "收藏集已更新", + "reading-list-deleted": "阅读列表已删除", + "reading-list-updated": "阅读列表已更新", + "confirm-delete-reading-list": "您确定要删除阅读列表吗?此操作无法撤销。", + "item-removed": "已移除条目", + "nothing-to-remove": "没有需要移除的内容", + "series-added-to-reading-list": "系列已添加到阅读列表中", + "volumes-added-to-reading-list": "卷已添加到阅读列表中", + "chapter-added-to-reading-list": "章节已添加到阅读列表中", + "multiple-added-to-reading-list": "章节和卷已添加到阅读列表中", + "select-files-warning": "您需要选择文件才能继续操作", + "reading-list-imported": "阅读列表已导入", + "incognito-off": "隐身模式已关闭。进度将开始被跟踪。", + "email-service-reset": "电子邮件服务已重置", + "email-service-reachable": "电子邮件服务可访问", + "email-service-unresponsive": "电子邮件服务的URL没有响应。", + "refresh-covers-queued": "已为{{name}}排队刷新封面", + "library-file-analysis-queued": "已为{{name}}排队进行资料库文件分析", + "entity-read": "{{name}}已标记为已读", + "entity-unread": "{{name}}已标记为未读", + "mark-read": "标记为已读", + "mark-unread": "标记为未读", + "series-removed-want-to-read": "已移除“想读”中的系列", + "series-deleted": "系列已移除", + "file-send-to": "文件已通过邮件发送给{{name}}", + "theme-missing": "当前的主题已不存在。请刷新页面。", + "email-sent": "已发送邮件到{{email}}", + "k+-license-saved": "许可密钥已保存,但不是有效的。点击检查以重新验证订阅。首次注册可能需要一分钟来传播。", + "k+-unlocked": "Kavita+已解锁!", + "k+-error": "激活许可证时出现错误,请重试。", + "k+-delete-key": "这将只删除Kavita的许可密钥并显示购买链接。这不会取消您的订阅!请仅在得到支持团队指示时使用此选项!", + "library-deleted": "已移除资料库 {{name}}", + "copied-to-clipboard": "已复制到剪贴板", + "book-settings-info": "您可以在菜单中修改图书设置并将这些设置保存为所有图书的设置,以及查看目录。", + "no-next-chapter": "找不到下一个{{entity}}", + "no-prev-chapter": "找不到上一个{{entity}}", + "load-next-chapter": "已加载下一个{{entity}}", + "load-prev-chapter": "已加载上一个{{entity}}", + "account-registration-complete": "账户注册完成", + "account-migration-complete": "账户迁移完成", + "password-reset": "密码重置", + "password-updated": "密码已更新", + "forced-scan-queued": "已开始强制扫描{{name}}", + "library-created": "资料库创建成功。已开始扫描。", + "anilist-token-updated": "已更新AniList令牌", + "age-restriction-updated": "年龄限制已更新", + "email-sent-to-no-existing": "已向{{email}}发送确认邮件。", + "email-sent-to": "已向您的旧电子邮件地址发送确认邮件。", + "change-email-private": "服务器不可公开访问。请向管理员索取您的确认链接,该链接可以从日志中获取", + "device-updated": "设备已更新", + "device-created": "设备已创建", + "confirm-regen-covers": "刷新封面将强制重新生成所有封面图片。这是一项繁重的运算。您确定执行,而不想使用扫描操作代替吗?", + "alert-long-running": "这是一个长时间运行的任务。请等待其完成后再次进行操作。", + "confirm-delete-multiple-series": "您确定要删除{{count}}个系列吗?这不会修改磁盘上的文件。", + "confirm-delete-series": "您确定要删除此系列吗?这不会修改磁盘上的文件。", + "alert-bad-theme": "主题中存在无效或不安全的CSS。请联系管理员进行修正。将默认为暗色主题。", + "confirm-library-delete": "您确定要删除{{name}}资料库吗?此操作无法撤销。", + "confirm-library-type-change": "更改资料库类型将触发使用不同解析规则的新扫描,可能导致系列被重新创建,进而可能丢失进度和书签。在进行此操作之前,建议您进行备份。确定要继续吗?", + "confirm-download-size": "{{entityType}}的大小为{{size}}。确定要继续吗?", + "list-doesnt-exist": "此列表不存在" }, "actionable": { - "scan-library": "", - "refresh-covers": "", - "analyze-files": "", - "settings": "", - "edit": "", - "mark-as-read": "", - "mark-as-unread": "", - "scan-series": "", - "add-to": "", - "add-to-want-to-read": "", - "remove-from-want-to-read": "", - "remove-from-on-deck": "", - "others": "", - "add-to-reading-list": "", - "add-to-collection": "", - "send-to": "", - "delete": "", - "download": "", - "read-incognito": "", - "details": "", - "view-series": "", - "clear": "", - "import-cbl": "" + "scan-library": "扫描资料库", + "refresh-covers": "刷新封面", + "analyze-files": "分析文件", + "settings": "设置", + "edit": "编辑", + "mark-as-read": "标记为已读", + "mark-as-unread": "标记为未读", + "scan-series": "扫描此系列", + "add-to": "添加到", + "add-to-want-to-read": "添加到想读", + "remove-from-want-to-read": "从想读中移除", + "remove-from-on-deck": "从最近阅读中移除", + "others": "其他", + "add-to-reading-list": "添加到阅读清单", + "add-to-collection": "添加到收藏", + "send-to": "发送到", + "delete": "删除", + "download": "下载", + "read-incognito": "隐身阅读", + "details": "详情", + "view-series": "查看系列", + "clear": "清空", + "import-cbl": "导入CBL文件", + "read": "阅读", + "add-rule-group-or": "添加规则组(或)", + "add-rule-group-and": "添加规则组(与)", + "remove-rule-group": "移除规则组" }, "preferences": { - "left-to-right": "", - "right-to-left": "", - "horizontal": "", - "vertical": "", - "automatic": "", - "fit-to-height": "", - "fit-to-width": "", - "original": "", - "fit-to-screen": "", - "no-split": "", - "webtoon": "", - "single": "", - "double": "", - "double-manga": "", - "scroll": "", - "1-column": "", - "2-column": "", - "cards": "", - "list": "", - "up-to-down": "" + "left-to-right": "从左到右", + "right-to-left": "从右到左", + "horizontal": "横向", + "vertical": "竖向", + "automatic": "自动", + "fit-to-height": "适应高度", + "fit-to-width": "适应宽度", + "original": "原始尺寸", + "fit-to-screen": "适应屏幕", + "no-split": "不分割", + "webtoon": "Webtoon", + "single": "单页", + "double": "双页", + "double-manga": "双页 (Manga)", + "scroll": "滚屏", + "1-column": "单列", + "2-column": "双列", + "cards": "卡片", + "list": "列表", + "up-to-down": "从上到下" }, "validation": { - "required-field": "", - "valid-email": "", - "password-validation": "" + "required-field": "必填项", + "valid-email": "必须输入有效的电子邮件地址", + "password-validation": "密码长度必须在6到32个字符之间" }, "entity-type": { - "volume": "", - "chapter": "", - "series": "", - "bookmark": "", - "logs": "" + "volume": "卷", + "chapter": "章节", + "series": "系列", + "bookmark": "书签", + "logs": "日志" }, "common": { - "reset-to-default": "", - "close": "", - "cancel": "", - "create": "", - "save": "", - "reset": "", - "add": "", - "apply": "", - "delete": "", - "edit": "", - "help": "", - "submit": "", - "email": "", - "read": "", - "loading": "", - "username": "", - "password": "", - "promoted": "", - "select-all": "", - "deselect-all": "", - "series-count": "", - "item-count": "", - "book-num": "", - "issue-hash-num": "", - "issue-num": "", - "chapter-num": "", - "volume-num": "" + "reset-to-default": "重置为默认值", + "close": "关闭", + "cancel": "取消", + "create": "创建", + "save": "保存", + "reset": "重置", + "add": "添加", + "apply": "应用", + "delete": "删除", + "edit": "编辑", + "help": "帮助", + "submit": "提交", + "email": "电子邮件", + "read": "阅读", + "loading": "正在加载…", + "username": "用户名", + "password": "密码", + "promoted": "推广", + "select-all": "全选", + "deselect-all": "取消全选", + "series-count": "{{num}}系列", + "item-count": "{{num}}条目", + "book-num": "书籍", + "issue-hash-num": "问题#", + "issue-num": "问题", + "chapter-num": "章节", + "volume-num": "卷" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "向上滚动以返回上一章节", + "continuous-reading-prev-chapter": "上一章节", + "continuous-reading-next-chapter-alt": "向上滚动以进入下一章节", + "continuous-reading-next-chapter": "下一章节" + }, + "metadata-builder": { + "or": "匹配以下任意项", + "and": "匹配以下所有项", + "add-rule": "添加规则", + "remove-rule": "移除行" + }, + "filter-field-pipe": { + "characters": "角色", + "collection-tags": "收藏标签", + "colorist": "上色师", + "cover-artist": "封面设计", + "editor": "编辑", + "formats": "格式", + "genres": "类别", + "inker": "上墨师", + "languages": "语言", + "read-progress": "阅读进度", + "read-time": "阅读时长", + "release-year": "发行年份", + "series-name": "系列名称", + "summary": "内容简介", + "tags": "标签", + "translators": "译者", + "writers": "作者", + "age-rating": "年龄分级", + "libraries": "资料库", + "publication-status": "出版状态", + "publisher": "出版社", + "user-rating": "用户评分", + "letterer": "排版", + "penciller": "线稿师", + "path": "路径", + "file-path": "文件路径" + }, + "filter-comparison-pipe": { + "begins-with": "开始于", + "contains": "包含", + "equal": "等于", + "greater-than": "大于", + "greater-than-or-equal": "大于或等于", + "less-than": "小于", + "less-than-or-equal": "小于或等于", + "matches": "匹配", + "not-equal": "不等于", + "ends-with": "以结束", + "is-before": "早于", + "is-after": "晚于", + "is-in-last": "在最近", + "is-not-in-last": "不在最近", + "does-not-contain": "不包含", + "must-contains": "必须包含" + }, + "cover-image-size": { + "default": "默认 (320x455)", + "medium": "中(640x909)", + "large": "大(900x1277)", + "xlarge": "特大(1265x1795)" } } diff --git a/UI/Web/src/assets/langs/zh_Hant.json b/UI/Web/src/assets/langs/zh_Hant.json new file mode 100644 index 0000000000..0d26fcd9aa --- /dev/null +++ b/UI/Web/src/assets/langs/zh_Hant.json @@ -0,0 +1,1757 @@ +{ + "login": { + "title": "登入", + "username": "{{common.username}}", + "password": "{{common.password}}", + "password-validation": "{{validation.password-validation}}", + "forgot-password": "忘記密碼?", + "submit": "{{common.submit}}" + }, + "dashboard": { + "no-libraries": "還沒有設定資料庫,設定位置在", + "server-settings-link": "伺服器設定", + "not-granted": "您沒有任何資料庫的訪問權限。", + "on-deck-title": "準備就緒", + "recently-updated-title": "最近更新系列", + "recently-added-title": "新增系列" + }, + "edit-user": { + "edit": "{{common.edit}}", + "close": "{{common.close}}", + "username": "{{common.username}}", + "required": "{{validation.required-field}}", + "email": "{{common.email}}", + "not-valid-email": "{{validation.valid-email}}", + "cancel": "{{common.cancel}}", + "saving": "儲存", + "update": "更新" + }, + "user-scrobble-history": { + "title": "Scrobble歷史", + "description": "在這裡您可以找到與您帳號關聯的所有Scrobble事件。為了使事件存在,您必須設定scrobble事件提供程式,所有已處理的事件將在一個月後清除。如果存在未處理的事件,這些事件很可能無法在上游形成匹配。請聯繫您的管理員進行修正。", + "filter-label": "篩選", + "created-header": "已創建", + "last-modified-header": "上一次修改", + "type-header": "類型", + "series-header": "系列", + "data-header": "資料", + "is-processed-header": "已處理", + "no-data": "沒有資料", + "volume-and-chapter-num": "第{{v}}卷 第{{n}}章", + "rating": "評分{{r}}", + "not-applicable": "不適用", + "processed": "處理", + "not-processed": "未處理" + }, + "scrobble-event-type-pipe": { + "chapter-read": "閱讀進度", + "score-updated": "更新評分", + "want-to-read-add": "添加想讀", + "want-to-read-remove": "移除想讀", + "review": "查看更新" + }, + "spoiler": { + "click-to-show": "點擊查看據透" + }, + "review-series-modal": { + "title": "編輯評論", + "tagline-label": "標語", + "review-label": "評論", + "close": "{{common.close}}", + "save": "{{common.save}}" + }, + "review-card-modal": { + "close": "{{common.close}}", + "user-review": "{{username}}的評論", + "external-mod": "(外部的)", + "go-to-review": "前往評論" + }, + "review-card": { + "your-review": "這是您的評論", + "external-review": "外部評論", + "local-review": "評論", + "rating-percentage": "評分{{r}}%" + }, + "want-to-read": { + "title": "想讀", + "series-count": "{{common.series-count}}", + "no-items": "沒有東西,嘗試添加一個系列。", + "no-items-filtered": "沒有項目符合您當前的過濾條件。" + }, + "user-preferences": { + "title": "使用者面板", + "pref-description": "這些是綁定到您帳戶的全局設置。", + "account-tab": "帳號", + "preferences-tab": "優先", + "3rd-party-clients-tab": "第三方帳號", + "theme-tab": "主題", + "devices-tab": "裝置", + "stats-tab": "統計資料", + "scrobbling-tab": "塗鴉", + "success-toast": "更新使用者偏好", + "global-settings-title": "全域設定", + "page-layout-mode-label": "頁面佈局模式", + "page-layout-mode-tooltip": "在系列詳細信息頁面上將項目顯示為卡片或列表視圖。", + "locale-label": "本地", + "locale-tooltip": "Kavita使用的語言", + "blur-unread-summaries-label": "模糊未讀摘要", + "blur-unread-summaries-tooltip": "模糊沒有閱讀進度的捲或章節的摘要文本(避免劇透)", + "prompt-on-download-label": "下載時提示", + "prompt-on-download-tooltip": "下載大小超過{{size}}MB時提示", + "disable-animations-label": "禁用動畫", + "disable-animations-tooltip": "關閉網站中的動畫,對於電子書閱讀器很有用。", + "collapse-series-relationships-label": "摺疊系列", + "collapse-series-relationships-tooltip": "Kavita是否展示沒有關係的系列或前傳", + "share-series-reviews-label": "分享系列評論", + "share-series-reviews-tooltip": "Kavita是否包含您對其他使用者的系列的評論", + "image-reader-settings-title": "影像讀取器", + "reading-direction-label": "閱讀方向", + "reading-direction-tooltip": "單擊方向或滑動畫面移動到下一頁。", + "scaling-option-label": "縮放選項", + "scaling-option-tooltip": "如何將影像縮放到螢幕大小。", + "page-splitting-label": "頁面分割", + "page-splitting-tooltip": "如何分割全寬影像(擊左右影像合併)", + "reading-mode-label": "閱讀模式", + "layout-mode-label": "佈局模式", + "layout-mode-tooltip": "將單個影像或兩個並排影像顯示到螢幕上", + "background-color-label": "背景顏色", + "auto-close-menu-label": "自動關閉選單", + "show-screen-hints-label": "顯示螢幕提示", + "emulate-comic-book-label": "模仿漫畫", + "swipe-to-paginate-label": "滑動到分頁", + "book-reader-settings-title": "圖書閱讀器", + "tap-to-paginate-label": "跳轉到分頁", + "tap-to-paginate-tooltip": "圖書閱讀器的畫面兩側是否允許點擊以移到上一頁/下一頁", + "immersive-mode-label": "沉浸式模式", + "immersive-mode-tooltip": "這將隱藏點擊閱讀器文檔後的菜單,然後點擊以分頁", + "reading-direction-book-label": "閱讀方向", + "reading-direction-book-tooltip": "單擊方向移動到下一頁。 從右到左意味著您單擊屏幕左側以移至下一頁。", + "font-family-label": "字體系列", + "font-family-tooltip": "要加載的字體系列。 默認將加載書籍的默認字體", + "writing-style-label": "寫作風格", + "writing-style-tooltip": "更改內文的方向。 水平方向是從左到右,垂直方向是從上到下。", + "layout-mode-book-label": "佈局模式", + "layout-mode-book-tooltip": "內容應該如何佈局。 捲軸就像書裡包裝的那樣。 1 或 2 列適合設備的高度,每頁適合 1 或 2 列內文", + "color-theme-book-label": "主題顏色", + "color-theme-book-tooltip": "什麼顏色主題適用於圖書閱讀器內容和菜單", + "font-size-book-label": "字體大小", + "line-height-book-label": "單行間距", + "line-height-book-tooltip": "書的行距是多少", + "margin-book-label": "利潤", + "margin-book-tooltip": "屏幕每一側的間距是多少。 無論此設置如何,這都會在移動設備上覆蓋為 0。", + "clients-opds-alert": "此伺服器上未啟用 OPDS。 這不會影響 Tachiyomi 使用者。", + "clients-opds-description": "所有第三方客戶端都將使用 API 密鑰或下面的連接 URL。 這些就像密碼一樣,請保密。", + "clients-api-key-tooltip": "API 密鑰就像一個密碼。 保守秘密,保證安全。", + "clients-opds-url-tooltip": "OPDS連結", + "reset": "{{common.reset}}", + "save": "{{common.save}}" + }, + "user-holds": { + "title": "Scrobble Holds", + "description": "這是用戶管理的系列列表,不會被記錄到上游提供商。 您可以隨時刪除系列,下一個可亂碼事件(閱讀進度、評分、想要閱讀狀態)將觸發事件。" + }, + "theme-manager": { + "title": "主題管理器", + "looking-for-theme": "正在尋找淺色或電子墨水主題? 我們有一些自定義主題,您可以在我們的 ", + "looking-for-theme-continued": "主題github。", + "scan": "掃描", + "site-themes": "網站主題", + "set-default": "預設設定", + "apply": "{{common.apply}}", + "applied": "應用", + "updated-toastr": "網站預設值已更新為{{name}}", + "scan-queued": "掃描網站主題已進入列隊" + }, + "theme": { + "theme-dark": "黑暗", + "theme-black": "黑色", + "theme-paper": "紙", + "theme-white": "白" + }, + "restriction-selector": { + "title": "年齡限制", + "description": "選擇此選項後,將從結果中刪除至少有一項大於所選限制的所有系列和閱讀列表。", + "not-applicable-for-admins": "這不適用於管理員。", + "age-rating-label": "年齡分級", + "no-restriction": "沒有限制", + "include-unknowns-label": "包括未知物", + "include-unknowns-tooltip": "如果屬實,則允許未知人員參加,但有年齡限制。 這可能會導致未標記的媒體洩露給有年齡限制的用戶。" + }, + "site-theme-provider-pipe": { + "system": "系統", + "user": "使用者" + }, + "manage-devices": { + "title": "裝置管理員", + "description": "本部分供您設置無法通過網路瀏覽器連接到 Kavita 且具有接受文件的電子信箱地址的裝置。", + "devices-title": "裝置", + "no-devices": "尚未設定任何裝置", + "platform-label": "平台: ", + "email-label": "電子信箱: ", + "add": "{{common.add}}", + "delete": "{{common.delete}}", + "edit": "{{common.edit}}" + }, + "edit-device": { + "device-name-label": "裝置名稱", + "email-label": "{{common.email}}", + "email-tooltip": "此接受文件將用於通過\"電子郵件\"發送到", + "device-platform-label": "設備", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}" + }, + "change-password": { + "password-label": "{{common.password}}", + "current-password-label": "當前密碼", + "new-password-label": "新密碼", + "confirm-password-label": "確認密碼", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "required-field": "{{validation.required-field}}", + "passwords-must-match": "密碼必須匹配", + "permission-error": "您無權更改您的密碼。聯繫服務器管理員。" + }, + "change-email": { + "email-label": "{{common.email}}", + "current-password-label": "當前密碼", + "email-not-confirmed": "此電子信箱未得到確認", + "email-updated-title": "電子信箱已更新", + "email-updated-description": "您可以使用下面的鏈接來確認您帳戶的電子郵件。 如果您的服務器可以從外部訪問,則會向該郵箱發送一封電子郵件,並且可以使用該鏈接來確認該電子郵件。", + "setup-user-account": "設定帳號", + "invite-url-label": "邀請連結", + "invite-url-tooltip": "複製此內容並貼到新分頁中", + "permission-error": "您無權更改您的電子郵件。 聯繫伺服器管理員。", + "required-field": "{{validation.required-field}}", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "change-age-restriction": { + "age-restriction-label": "年齡限制", + "unknowns": "未知數", + "reset": "{{common.reset}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "api-key": { + "copy": "複製", + "show": "展示", + "regen-warning": "重新生成 API 密鑰將使現有使用端失效。", + "no-key": "錯誤 - 未設置密鑰", + "confirm-reset": "這將使您設置的任何 OPDS 配置無效。 你確定要繼續嗎?", + "key-reset": "API密鑰重置" + }, + "scrobbling-providers": { + "title": "Scrobbling供應商", + "requires": "此功能需要有效的{{product}}授權", + "token-expired": "Token已過期", + "no-token-set": "未設定Token", + "token-set": "設定Token", + "generate": "產生", + "instructions": "首次使用的用戶應點擊下面的“{{scrobbling-providers.generate}}”以允許 Kavita+ 與 {{service}} 連線。 授權程序後,將Token複製並貼到下面的輸入中。 您可以隨時重新生成您的Token。", + "token-input-label": "Token:{{service}}", + "edit": "{{common.edit}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "typeahead": { + "locked-field": "已鎖定", + "close": "{{common.close}}", + "loading": "{{common.loading}}", + "add-item": "新增{{item}}", + "no-data": "沒有資料", + "add-custom-item": ",輸入以添加自定義項目" + }, + "generic-list-modal": { + "close": "{{common.close}}", + "clear": "清除", + "filter": "篩選", + "open-filtered-search": "打開對{{item}}的過濾" + }, + "user-stats-info-cards": { + "total-pages-read-label": "總閱讀頁數", + "total-pages-read-tooltip": "{{user-stats-info-cards.total-pages-read-label}}: {{value}}", + "total-words-read-label": "閱讀總字數", + "total-words-read-tooltip": "{{user-stats-info-cards.total-words-read-label}}: {{value}}", + "time-spent-reading-label": "閱讀時間", + "time-spent-reading-tooltip": "{{user-stats-info-cards.time-spent-reading-label}}: {{value}}", + "chapters-read-label": "閱讀章節", + "chapters-read-tooltip": "{{user-stats-info-cards.chapters-read-label}}: {{value}}", + "avg-reading-per-week-label": "平均閱讀量/週", + "last-active-label": "最後登錄", + "chapters": "{{value}}章" + }, + "user-stats": { + "library-read-progress-title": "圖書館閱讀進度", + "read-percentage": "% 讀" + }, + "top-readers": { + "title": "熱門讀者", + "time-selection-label": "時間", + "comics-label": "漫畫:{{value}}小時", + "manga-label": "漫畫:{{value}}小時", + "books-label": "書籍:{{value}}小時", + "this-week": "{{time-periods.this-week}}", + "last-7-days": "{{time-periods.last-7-days}}", + "last-30-days": "{{time-periods.last-30-days}}", + "last-90-days": "{{time-periods.last-90-days}}", + "last-year": "{{time-periods.last-year}}", + "all-time": "{{time-periods.all-time}}" + }, + "role-selector": { + "title": "角色" + }, + "directory-picker": { + "title": "選擇目錄", + "close": "{{common.close}}", + "path-label": "路徑", + "path-placeholder": "輸入路徑或選擇路徑", + "instructions": "選擇一個文件夾以查看。沒有看到您的目錄? 先嘗試檢查。", + "type-header": "類型", + "name-header": "名子", + "cancel": "{{common.cancel}}", + "share": "分享", + "help": "{{common.help}}" + }, + "library-access-modal": { + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "title": "訪問圖書館", + "close": "{{common.close}}", + "reset": "{{common.reset}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}", + "no-data": "尚未設定資料庫。" + }, + "time-periods": { + "this-week": "這周", + "last-7-days": "前7天", + "last-30-days": "前30天", + "last-90-days": "前90天", + "last-year": "前一年", + "all-time": "整天" + }, + "device-platform-pipe": { + "custom": "" + }, + "day-of-week-pipe": { + "monday": "週一", + "tuesday": "週二", + "wednesday": "週三", + "thursday": "週四", + "friday": "週五", + "saturday": "週六", + "sunday": "週日" + }, + "cbl-import-result-pipe": { + "success": "成功", + "partial": "部分", + "failure": "失敗" + }, + "cbl-conflict-reason-pipe": { + "all-series-missing": "您的帳戶無法訪問列表中的所有系列,或者 Kavita 列表中沒有任何內容。", + "chapter-missing": "{{series}}:Kavita缺少第{{chapter}}章,該項目將被跳過。", + "empty-file": "cbl 檔案為空,無需執行任何操作。", + "name-conflict": "您的帳號中已存在與 cbl 文件匹配的閱讀清單({{readingListName}})。", + "series-collision": "該系列{{seriesLink}}與資料庫中的系列名稱相同。", + "series-missing": "Kavita中缺少系列{{series}},或者您的帳號沒有權限。 該系列的所有項目都將跳過導入。", + "volume-missing": "{{series}}:Kavita中缺少卷{{volume}},或者您的帳號沒有權限。 所有具有該卷號的項目都將被跳過。", + "all-chapter-missing": "所有章節均無法與Kavita中的章節匹配。", + "invalid-file": "文件已損壞或與預期的標籤或規格不匹配。", + "success": "{{series}}卷{{volume}}章節{{chapter}}映射成功。" + }, + "time-duration-pipe": { + "hours": "{{value}}小時", + "minutes": "{{value}}分鐘", + "days": "{{value}}天", + "months": "{{value}}月", + "years": "{{value}}年" + }, + "time-ago-pipe": { + "never": "從不", + "just-now": "現在", + "min-ago": "1分鐘前", + "mins-ago": "{{value}}分鐘前", + "hour-ago": "1小時前", + "hours-ago": "{{value}}小時前", + "day-ago": "1天前", + "days-ago": "{{value}}天前", + "month-ago": "1個月前", + "months-ago": "{{value}}個月前", + "year-ago": "1年前", + "years-ago": "{{value}}年前" + }, + "relationship-pipe": { + "adaptation": "適應", + "alternative-setting": "替代設定", + "alternative-version": "替代版本", + "character": "特點", + "contains": "包含", + "doujinshi": "同人", + "other": "其他", + "prequel": "前傳", + "sequel": "續集", + "side-story": "番外", + "spin-off": "拆分", + "parent": "家長", + "edition": "編輯" + }, + "publication-status-pipe": { + "ongoing": "進行中", + "hiatus": "差距", + "completed": "完全的", + "cancelled": "取消", + "ended": "完結" + }, + "person-role-pipe": { + "artist": "畫家", + "character": "特色", + "colorist": "上色師", + "cover-artist": "封面畫家", + "editor": "編輯", + "inker": "上墨", + "letterer": "文字書寫", + "penciller": "線稿", + "publisher": "出版", + "writer": "作者", + "other": "其他" + }, + "manga-format-pipe": { + "epub": "EPUB", + "archive": "檔案", + "image": "圖片", + "pdf": "PDF", + "unknown": "未知" + }, + "library-type-pipe": { + "book": "書籍", + "comic": "漫畫", + "manga": "漫畫" + }, + "age-rating-pipe": { + "unknown": "未知", + "early-childhood": "童書", + "adults-only": "18+", + "everyone": "所有人", + "everyone-10-plus": "10+", + "g": "普遍級", + "kids-to-adults": "兒童到成人", + "mature": "成熟", + "ma15-plus": "15+", + "mature-17-plus": "17+", + "rating-pending": "評級待定", + "teen": "青少年", + "x18-plus": "X18+", + "not-applicable": "不適用", + "pg": "PG", + "r18-plus": "R18+" + }, + "reset-password": { + "title": "重置密碼", + "description": "輸入您帳戶的電子郵件。如果資料有效,Kavita會向您發送一封電子郵件,否則請向管理員詢問連結。", + "email-label": "{{common.email}}", + "required-field": "{{validation.required-field}}", + "valid-email": "{{validation.valid-email}}", + "submit": "{{common.submit}}" + }, + "reset-password-modal": { + "title": "重置{{username}}的密碼", + "new-password-label": "新密碼", + "error-label": "錯誤: ", + "close": "{{common.close}}", + "cancel": "{{common.cancel}}", + "save": "{{common.save}}" + }, + "all-series": { + "title": "全部系列", + "series-count": "" + }, + "announcements": { + "title": "" + }, + "changelog": { + "installed": "", + "download": "", + "published-label": "", + "available": "", + "description": "", + "description-continued": "" + }, + "invite-user": { + "title": "", + "close": "{{common.close}}", + "description": "", + "email": "", + "required-field": "", + "setup-user-title": "", + "setup-user-description": "", + "setup-user-account": "", + "setup-user-account-tooltip": "", + "invite-url-label": "", + "invite": "", + "inviting": "", + "cancel": "{{common.cancel}}" + }, + "library-selector": { + "title": "", + "select-all": "{{common.select-all}}", + "deselect-all": "{{common.deselect-all}}", + "no-data": "" + }, + "license": { + "title": "", + "manage": "", + "invalid-license-tooltip": "", + "check": "", + "cancel": "", + "edit": "", + "buy": "", + "activate": "", + "renew": "", + "no-license-key": "", + "license-valid": "", + "license-not-valid": "", + "loading": "", + "activate-description": "", + "activate-license-label": "", + "activate-email-label": "", + "activate-delete": "", + "activate-save": "{{common.save}}" + }, + "book-line-overlay": { + "copy": "複製", + "bookmark": "", + "close": "{{common.close}}", + "required-field": "{{common.required-field}}", + "bookmark-label": "", + "save": "{{common.save}}" + }, + "book-reader": { + "title": "", + "page-label": "", + "pagination-header": "", + "go-to-page": "", + "go-to-last-page": "", + "prev-page": "", + "next-page": "", + "prev-chapter": "", + "next-chapter": "", + "skip-header": "", + "virtual-pages": "", + "settings-header": "", + "table-of-contents-header": "", + "bookmarks-header": "", + "toc-header": "", + "loading-book": "", + "go-back": "", + "incognito-mode-alt": "", + "incognito-mode-label": "", + "next": "", + "previous": "", + "go-to-page-prompt": "" + }, + "personal-table-of-contents": { + "no-data": "", + "page": "", + "delete": "" + }, + "confirm-email": { + "title": "", + "description": "", + "error-label": "", + "username-label": "", + "password-label": "", + "email-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "confirm-email-change": { + "title": "", + "non-confirm-description": "", + "confirm-description": "", + "success": "" + }, + "confirm-reset-password": { + "title": "", + "description": "", + "password-label": "", + "required-field": "", + "submit": "", + "password-validation": "" + }, + "register": { + "title": "", + "description": "", + "username-label": "", + "email-label": "", + "email-tooltip": "", + "password-label": "", + "required-field": "", + "valid-email": "", + "password-validation": "", + "register": "" + }, + "series-detail": { + "page-settings-title": "", + "close": "{{common.close}}", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-option-card": "", + "layout-mode-option-list": "", + "continue-from": "", + "read": "", + "continue": "", + "read-options-alt": "", + "incognito": "", + "remove-from-want-to-read": "", + "add-to-want-to-read": "", + "edit-series-alt": "", + "download-series--tooltip": "", + "downloading-status": "", + "user-reviews-alt": "", + "storyline-tab": "", + "books-tab": "", + "volumes-tab": "", + "specials-tab": "", + "related-tab": "", + "recommendations-tab": "", + "send-to": "", + "no-pages": "", + "no-chapters": "", + "cover-change": "" + }, + "series-metadata-detail": { + "links-title": "", + "genres-title": "", + "tags-title": "", + "collections-title": "", + "reading-lists-title": "", + "writers-title": "", + "cover-artists-title": "", + "characters-title": "", + "colorists-title": "", + "editors-title": "", + "inkers-title": "", + "letterers-title": "", + "translators-title": "", + "pencillers-title": "", + "publishers-title": "", + "promoted": "", + "see-more": "", + "see-less": "" + }, + "badge-expander": { + "more-items": "" + }, + "read-more": { + "read-more": "", + "read-less": "" + }, + "update-notification-modal": { + "title": "", + "close": "", + "help": "", + "download": "" + }, + "side-nav-companion-bar": { + "page-settings-title": "", + "open-filter-and-sort": "", + "close-filter-and-sort": "", + "filter-and-sort-alt": "" + }, + "side-nav": { + "home": "", + "want-to-read": "", + "collections": "", + "reading-lists": "", + "bookmarks": "", + "filter-label": "", + "all-series": "", + "clear": "清除", + "donate": "" + }, + "library-settings-modal": { + "close": "", + "edit-title": "", + "add-title": "", + "general-tab": "", + "folder-tab": "", + "cover-tab": "", + "advanced-tab": "", + "name-label": "", + "library-name-unique": "", + "last-scanned-label": "", + "type-label": "", + "type-tooltip": "", + "folder-description": "", + "browse": "", + "help-us-part-1": "", + "help-us-part-2": "", + "help-us-part-3": "", + "naming-conventions-part-1": "", + "naming-conventions-part-2": "", + "naming-conventions-part-3": "", + "cover-description": "", + "cover-description-extra": "", + "manage-collection-label": "", + "manage-collection-tooltip": "", + "manage-reading-list-label": "", + "manage-reading-list-tooltip": "", + "allow-scrobbling-label": "", + "allow-scrobbling-tooltip": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "include-in-dashboard-label": "", + "include-in-dashboard-tooltip": "", + "include-in-recommendation-label": "", + "include-in-recommendation-tooltip": "", + "include-in-search-label": "", + "include-in-search-tooltip": "", + "force-scan": "", + "force-scan-tooltip": "", + "reset": "", + "cancel": "", + "next": "", + "save": "{{common.save}}", + "required-field": "" + }, + "reader-settings": { + "general-settings-title": "", + "font-family-label": "", + "font-size-label": "", + "line-spacing-label": "", + "margin-label": "", + "reset-to-defaults": "", + "reader-settings-title": "", + "reading-direction-label": "", + "right-to-left": "", + "left-to-right": "", + "horizontal": "", + "vertical": "", + "writing-style-label": "", + "writing-style-tooltip": "", + "tap-to-paginate-label": "", + "tap-to-paginate-tooltip": "", + "on": "", + "off": "", + "immersive-mode-label": "", + "immersive-mode-tooltip": "", + "fullscreen-label": "", + "fullscreen-tooltip": "", + "exit": "", + "enter": "", + "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-tooltip": "", + "layout-mode-option-scroll": "", + "layout-mode-option-1col": "", + "layout-mode-option-2col": "", + "color-theme-title": "", + "theme-dark": "", + "theme-black": "", + "theme-white": "", + "theme-paper": "" + }, + "table-of-contents": { + "no-data": "" + }, + "bookmarks": { + "title": "", + "series-count": "", + "no-data": "", + "no-data-2": "", + "confirm-delete": "", + "confirm-single-delete": "", + "delete-success": "", + "delete-single-success": "" + }, + "bulk-operations": { + "title": "", + "items-selected": "", + "mark-as-unread": "", + "mark-as-read": "", + "deselect-all": "" + }, + "card-detail-drawer": { + "general-tab": "", + "metadata-tab": "", + "cover-tab": "", + "info-tab": "", + "no-summary": "", + "writers-title": "", + "genres-title": "", + "publishers-title": "", + "tags-title": "", + "not-defined": "", + "read": "", + "unread": "", + "files": "", + "pages": "", + "added": "", + "size": "" + }, + "card-detail-layout": { + "total-items": "" + }, + "card-item": { + "cannot-read": "" + }, + "chapter-metadata-detail": { + "no-data": "", + "writers-title": "", + "publishers-title": "", + "characters-title": "", + "translators-title": "", + "letterers-title": "", + "colorists-title": "", + "inkers-title": "", + "pencillers-title": "", + "cover-artists-title": "", + "editors-title": "" + }, + "cover-image-chooser": { + "drag-n-drop": "", + "upload": "", + "upload-continued": "", + "url-label": "", + "load": "", + "back": "", + "reset-cover-tooltip": "", + "reset": "", + "image-num": "", + "apply": "", + "applied": "" + }, + "download-indicator": { + "progress": "" + }, + "edit-series-relation": { + "description-part-1": "", + "description-part-2": "", + "target-series": "", + "relationship": "", + "remove": "", + "add-relationship": "", + "parent": "" + }, + "entity-info-cards": { + "tags-title": "", + "characters-title": "", + "release-date-title": "", + "release-date-tooltip": "", + "age-rating-title": "", + "length-title": "", + "pages-count": "", + "words-count": "", + "reading-time-title": "", + "date-added-title": "", + "size-title": "", + "id-title": "", + "links-title": "", + "isbn-title": "", + "last-read-title": "", + "less-than-hour": "", + "range-hours": "", + "hour": "", + "hours": "", + "read-time-title": "" + }, + "series-info-cards": { + "release-date-title": "", + "release-year-tooltip": "", + "age-rating-title": "", + "language-title": "", + "publication-status-title": "", + "publication-status-tooltip": "", + "scrobbling-title": "", + "scrobbling-tooltip": "", + "on": "", + "off": "", + "disabled": "", + "format-title": "", + "last-read-title": "", + "length-title": "", + "read-time-title": "", + "less-than-hour": "", + "hour": "", + "hours": "", + "time-left-title": "", + "ongoing": "", + "pages-count": "", + "words-count": "" + }, + "bulk-add-to-collection": { + "title": "", + "promoted": "", + "close": "", + "filter-label": "", + "clear": "", + "no-data": "", + "loading": "", + "collection-label": "", + "create": "" + }, + "entity-title": { + "special": "", + "issue-num": "", + "chapter": "" + }, + "external-series-card": { + "open-external": "" + }, + "list-item": { + "read": "" + }, + "manage-alerts": { + "description-part-1": "", + "description-part-2": "", + "filter-label": "", + "clear-alerts": "", + "extension-header": "", + "file-header": "", + "comment-header": "", + "details-header": "" + }, + "manage-email-settings": { + "title": "", + "description": "", + "send-to-warning": "", + "email-url-label": "", + "email-url-tooltip": "", + "reset": "", + "test": "", + "host-name-label": "", + "host-name-tooltip": "", + "host-name-validation": "", + "reset-to-default": "", + "save": "{{common.save}}" + }, + "manage-library": { + "title": "", + "add-library": "", + "no-data": "", + "loading": "", + "last-scanned-title": "", + "shared-folders-title": "", + "type-title": "", + "scan-library": "", + "delete-library": "", + "delete-library-by-name": "", + "edit-library": "", + "edit-library-by-name": "" + }, + "manage-media-settings": { + "encode-as-description-part-1": "", + "encode-as-description-part-2": "", + "encode-as-description-part-3": "", + "encode-as-warning": "", + "media-warning": "", + "encode-as-label": "", + "encode-as-tooltip": "", + "bookmark-dir-label": "", + "bookmark-dir-tooltip": "", + "change": "", + "reset-to-default": "", + "reset": "", + "save": "{{common.save}}", + "media-issue-title": "", + "scrobble-issue-title": "", + "cover-image-size-label": "", + "cover-image-size-tooltip": "" + }, + "cover-image-size": { + "default": "", + "medium": "", + "large": "", + "xlarge": "" + }, + "manage-scrobble-errors": { + "description": "", + "filter-label": "", + "clear-errors": "", + "series-header": "", + "created-header": "", + "comment-header": "", + "edit-header": "", + "edit-item-alt": "" + }, + "default-date-pipe": { + "never": "" + }, + "manage-settings": { + "notice": "", + "restart-required": "", + "base-url-label": "", + "base-url-tooltip": "", + "ip-address-label": "", + "ip-address-tooltip": "", + "port-label": "", + "port-tooltip": "", + "backup-label": "", + "backup-tooltip": "", + "log-label": "", + "log-tooltip": "", + "logging-level-label": "", + "logging-level-tooltip": "", + "cache-size-label": "", + "cache-size-tooltip": "", + "on-deck-last-progress-label": "", + "on-deck-last-progress-tooltip": "", + "on-deck-last-chapter-add-label": "", + "on-deck-last-chapter-add-tooltip": "", + "allow-stats-label": "", + "allow-stats-tooltip-part-1": "", + "allow-stats-tooltip-part-2": "", + "send-data": "", + "opds-label": "", + "opds-tooltip": "", + "enable-opds": "", + "folder-watching-label": "", + "folder-watching-tooltip": "", + "enable-folder-watching": "", + "reset-to-default": "", + "reset": "", + "save": "{{common.save}}", + "cache-size-validation": "", + "field-required": "", + "max-logs-validation": "", + "min-logs-validation": "", + "min-days-validation": "", + "min-cache-validation": "", + "max-backup-validation": "", + "min-backup-validation": "", + "ip-address-validation": "", + "base-url-validation": "" + }, + "manage-system": { + "title": "", + "version-title": "", + "installId-title": "", + "more-info-title": "", + "home-page-title": "", + "wiki-title": "", + "discord-title": "", + "donations-title": "", + "source-title": "", + "feature-request-title": "" + }, + "manage-tasks-settings": { + "title": "", + "library-scan-label": "", + "library-scan-tooltip": "", + "library-database-backup-label": "", + "library-database-backup-tooltip": "", + "adhoc-tasks-title": "", + "job-title-header": "", + "description-header": "", + "action-header": "", + "reset-to-default": "", + "reset": "", + "save": "{{common.save}}", + "recurring-tasks-title": "", + "last-executed-header": "", + "cron-header": "", + "convert-media-task": "", + "convert-media-task-desc": "", + "convert-media-success": "", + "bust-cache-task": "", + "bust-cache-task-desc": "", + "bust-cache-task-success": "", + "clear-reading-cache-task": "", + "clear-reading-cache-task-desc": "", + "clear-reading-cache-task-success": "", + "clean-up-want-to-read-task": "", + "clean-up-want-to-read-task-desc": "", + "clean-up-want-to-read-task-success": "", + "backup-database-task": "", + "backup-database-task-desc": "", + "backup-database-task-success": "", + "download-logs-task": "", + "download-logs-task-desc": "", + "analyze-files-task": "", + "analyze-files-task-desc": "", + "analyze-files-task-success": "", + "check-for-updates-task": "", + "check-for-updates-task-desc": "" + }, + "manage-users": { + "title": "", + "invite": "", + "you-alt": "", + "pending-title": "", + "delete-user-tooltip": "", + "delete-user-alt": "", + "edit-user-tooltip": "", + "edit-user-alt": "", + "resend-invite-tooltip": "", + "resend-invite-alt": "", + "setup-user-tooltip": "", + "setup-user-alt": "", + "change-password-tooltip": "", + "change-password-alt": "", + "resend": "", + "setup": "", + "last-active-title": "", + "roles-title": "", + "none": "", + "never": "", + "online-now-tooltip": "", + "sharing-title": "", + "no-data": "", + "loading": "" + }, + "edit-collection-tags": { + "title": "", + "required-field": "", + "save": "{{common.save}}", + "close": "", + "cancel": "", + "general-tab": "", + "cover-image-tab": "", + "series-tab": "", + "name-label": "", + "name-validation": "", + "promote-label": "", + "promote-tooltip": "", + "summary-label": "", + "series-title": "", + "deselect-all": "", + "select-all": "{{common.select-all}}" + }, + "library-detail": { + "library-tab": "", + "recommended-tab": "" + }, + "library-recommended": { + "no-data": "", + "more-in-genre": "", + "rediscover": "", + "highly-rated": "", + "quick-catchups": "", + "quick-reads": "", + "on-deck": "" + }, + "admin-dashboard": { + "title": "", + "general-tab": "", + "users-tab": "", + "libraries-tab": "", + "media-tab": "", + "logs-tab": "", + "email-tab": "", + "tasks-tab": "", + "statistics-tab": "", + "system-tab": "", + "kavita+-tab": "", + "kavita+-desc-part-1": "", + "kavita+-desc-part-2": "", + "kavita+-desc-part-3": "" + }, + "collection-detail": { + "no-data": "", + "no-data-filtered": "", + "title-alt": "" + }, + "all-collections": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "carousel-reel": { + "prev-items": "", + "next-items": "" + }, + "draggable-ordered-list": { + "instructions-alt": "", + "reorder-label": "", + "remove-item-alt": "" + }, + "reading-lists": { + "title": "", + "item-count": "", + "no-data": "", + "create-one-part-1": "", + "create-one-part-2": "" + }, + "reading-list-item": { + "remove": "", + "read": "" + }, + "reading-list-detail": { + "item-count": "", + "page-settings-title": "", + "remove-read": "", + "order-numbers-label": "", + "continue": "", + "read": "", + "read-options-alt": "", + "incognito-alt": "", + "no-data": "", + "characters-title": "" + }, + "events-widget": { + "title-alt": "", + "dismiss-all": "", + "update-available": "", + "downloading-item": "", + "more-info": "", + "close": "", + "users-online-count": "", + "active-events-title": "", + "no-data": "" + }, + "shortcuts-modal": { + "title": "", + "close": "", + "prev-page": "", + "next-page": "", + "go-to": "", + "bookmark": "", + "double-click": "", + "close-reader": "", + "toggle-menu": "" + }, + "grouped-typeahead": { + "files": "", + "chapters": "", + "people": "", + "tags": "", + "genres": "", + "libraries": "", + "reading-lists": "", + "collections": "", + "close": "", + "loading": "" + }, + "nav-header": { + "skip-alt": "", + "search-series-alt": "", + "search-alt": "", + "promoted": "", + "no-data": "", + "scroll-to-top-alt": "", + "server-settings": "", + "settings": "", + "help": "", + "announcements": "", + "logout": "" + }, + "add-to-list-modal": { + "title": "", + "close": "", + "filter-label": "", + "promoted-alt": "", + "no-data": "", + "loading": "", + "reading-list-label": "", + "create": "" + }, + "edit-reading-list-modal": { + "title": "", + "general-tab": "", + "cover-image-tab": "", + "close": "", + "save": "", + "year-validation": "", + "month-validation": "", + "name-unique-validation": "", + "required-field": "", + "summary-label": "", + "year-label": "", + "month-label": "", + "ending-title": "", + "starting-title": "", + "promote-label": "", + "promote-tooltip": "" + }, + "import-cbl-modal": { + "close": "", + "title": "", + "import-description": "", + "validate-description": "", + "validate-warning": "", + "validate-no-issue": "", + "validate-no-issue-description": "", + "dry-run-description": "", + "prev": "", + "import": "", + "restart": "", + "next": "", + "import-step": "", + "validate-cbl-step": "", + "dry-run-step": "", + "final-import-step": "" + }, + "pdf-reader": { + "loading-message": "", + "incognito-mode": "", + "light-theme-alt": "", + "dark-theme-alt": "", + "close-reader-alt": "" + }, + "infinite-scroller": { + "continuous-reading-prev-chapter-alt": "", + "continuous-reading-prev-chapter": "", + "continuous-reading-next-chapter-alt": "", + "continuous-reading-next-chapter": "" + }, + "manga-reader": { + "back": "", + "save-globally": "", + "incognito-alt": "", + "incognito-title": "", + "shortcuts-menu-alt": "", + "prev-page-tooltip": "", + "next-page-tooltip": "", + "prev-chapter-tooltip": "", + "next-chapter-tooltip": "", + "first-page-tooltip": "", + "last-page-tooltip": "", + "left-to-right-alt": "", + "right-to-left-alt": "", + "reading-direction-tooltip": "", + "reading-mode-tooltip": "", + "collapse": "", + "fullscreen": "", + "settings-tooltip": "", + "image-splitting-label": "", + "image-scaling-label": "", + "height": "", + "width": "", + "original": "", + "auto-close-menu-label": "", + "swipe-enabled-label": "", + "enable-comic-book-label": "", + "brightness-label": "", + "first-time-reading-manga": "", + "layout-mode-switched": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "user-preferences-updated": "", + "emulate-comic-book-label": "" + }, + "metadata-filter": { + "filter-title": "", + "sort-by-label": "", + "ascending-alt": "", + "descending-alt": "", + "reset": "", + "apply": "", + "limit-label": "", + "format-label": "", + "libraries-label": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "read-progress-label": "", + "unread": "", + "read": "", + "in-progress": "", + "rating-label": "", + "age-rating-label": "", + "language-label": "", + "publication-status-label": "", + "series-name-label": "", + "series-name-tooltip": "", + "release-label": "", + "min": "", + "max": "" + }, + "sort-field-pipe": { + "sort-name": "", + "created": "", + "last-modified": "", + "last-chapter-added": "", + "time-to-read": "", + "release-year": "" + }, + "edit-series-modal": { + "title": "", + "general-tab": "", + "metadata-tab": "", + "people-tab": "", + "web-links-tab": "", + "cover-image-tab": "", + "related-tab": "", + "info-tab": "", + "collections-label": "", + "genres-label": "", + "tags-label": "", + "cover-artist-label": "", + "writer-label": "", + "publisher-label": "", + "penciller-label": "", + "letterer-label": "", + "inker-label": "", + "editor-label": "", + "colorist-label": "", + "character-label": "", + "translator-label": "", + "language-label": "", + "age-rating-label": "", + "publication-status-label": "", + "required-field": "", + "close": "", + "name-label": "", + "sort-name-label": "", + "localized-name-label": "", + "summary-label": "", + "release-year-label": "", + "web-link-description": "", + "web-link-label": "", + "add-link-alt": "", + "remove-link-alt": "", + "cover-image-description": "", + "save": "{{common.save}}", + "field-locked-alt": "已鎖定", + "info-title": "", + "library-title": "", + "format-title": "", + "created-title": "", + "last-read-title": "", + "last-added-title": "", + "last-scanned-title": "", + "folder-path-title": "", + "publication-status-title": "", + "total-pages-title": "", + "total-items-title": "", + "max-items-title": "", + "size-title": "", + "loading": "", + "added-title": "", + "last-modified-title": "", + "view-files": "", + "pages-title": "", + "chapter-title": "", + "volume-num": "", + "highest-count-tooltip": "", + "max-issue-tooltip": "" + }, + "day-breakdown": { + "title": "", + "x-axis-label": "", + "y-axis-label": "" + }, + "file-breakdown-stats": { + "format-title": "", + "format-tooltip": "", + "visualisation-label": "", + "data-table-label": "", + "extension-header": "", + "format-header": "", + "total-size-header": "", + "total-files-header": "", + "not-classified": "", + "total-file-size-title": "" + }, + "reading-activity": { + "title": "", + "legend-label": "", + "x-axis-label": "", + "y-axis-label": "", + "no-data": "", + "time-frame-label": "", + "this-week": "", + "last-7-days": "", + "last-30-days": "", + "last-90-days": "", + "last-year": "", + "all-time": "" + }, + "manga-format-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "format-header": "", + "count-header": "" + }, + "publication-status-stats": { + "title": "", + "visualisation-label": "", + "data-table-label": "", + "year-header": "", + "count-header": "" + }, + "server-stats": { + "total-series-label": "", + "total-series-tooltip": "", + "total-volumes-label": "", + "total-volumes-tooltip": "", + "total-files-label": "", + "total-files-tooltip": "", + "total-size-label": "", + "total-genres-label": "", + "total-genres-tooltip": "", + "total-tags-label": "", + "total-tags-tooltip": "", + "total-people-label": "", + "total-people-tooltip": "", + "total-read-time-label": "", + "total-read-time-tooltip": "", + "series": "", + "reads": "", + "release-years-title": "", + "most-active-users-title": "", + "popular-libraries-title": "", + "popular-series-title": "", + "recently-read-title": "", + "genre-count": "", + "tag-count": "", + "people-count": "", + "tags": "", + "people": "", + "genres": "" + }, + "errors": { + "series-doesnt-exist": "", + "collection-invalid-access": "", + "unknown-crit": "", + "user-not-auth": "", + "error-code": "", + "download": "", + "not-found": "", + "generic": "", + "rejected-cover-upload": "", + "invalid-confirmation-url": "", + "invalid-confirmation-email": "", + "invalid-password-reset-url": "" + }, + "metadata-builder": { + "or": "", + "and": "", + "add-rule": "", + "remove-rule": "" + }, + "filter-field-pipe": { + "age-rating": "", + "characters": "", + "collection-tags": "", + "colorist": "", + "cover-artist": "", + "editor": "", + "formats": "", + "genres": "", + "inker": "", + "languages": "", + "libraries": "", + "letterer": "", + "publication-status": "", + "penciller": "", + "publisher": "", + "read-progress": "", + "read-time": "", + "release-year": "", + "series-name": "", + "summary": "", + "tags": "", + "translators": "", + "user-rating": "", + "writers": "", + "path": "", + "file-path": "" + }, + "filter-comparison-pipe": { + "begins-with": "", + "contains": "", + "equal": "", + "greater-than": "", + "greater-than-or-equal": "", + "less-than": "", + "less-than-or-equal": "", + "matches": "", + "does-not-contain": "", + "not-equal": "", + "ends-with": "", + "is-before": "", + "is-after": "", + "is-in-last": "", + "is-not-in-last": "" + }, + "toasts": { + "regen-cover": "", + "no-pages": "", + "download-in-progress": "", + "scan-queued": "", + "server-settings-updated": "", + "reset-ip-address": "", + "reset-base-url": "", + "unauthorized-1": "", + "unauthorized-2": "", + "no-updates": "", + "confirm-delete-user": "", + "user-deleted": "", + "email-sent-to-user": "", + "click-email-link": "", + "series-added-to-collection": "", + "no-series-collection-warning": "", + "collection-updated": "", + "reading-list-deleted": "", + "reading-list-updated": "", + "confirm-delete-reading-list": "", + "item-removed": "", + "nothing-to-remove": "", + "series-added-to-reading-list": "", + "volumes-added-to-reading-list": "", + "chapter-added-to-reading-list": "", + "multiple-added-to-reading-list": "", + "select-files-warning": "", + "reading-list-imported": "", + "incognito-off": "", + "email-service-reset": "", + "email-service-reachable": "", + "email-service-unresponsive": "", + "refresh-covers-queued": "", + "library-file-analysis-queued": "", + "entity-read": "", + "entity-unread": "", + "mark-read": "", + "mark-unread": "", + "series-removed-want-to-read": "", + "series-deleted": "", + "file-send-to": "", + "theme-missing": "", + "email-sent": "", + "k+-license-saved": "", + "k+-unlocked": "", + "k+-error": "", + "k+-delete-key": "", + "library-deleted": "", + "copied-to-clipboard": "", + "book-settings-info": "", + "no-next-chapter": "", + "no-prev-chapter": "", + "load-next-chapter": "", + "load-prev-chapter": "", + "account-registration-complete": "", + "account-migration-complete": "", + "password-reset": "", + "password-updated": "", + "forced-scan-queued": "", + "library-created": "", + "anilist-token-updated": "", + "age-restriction-updated": "", + "email-sent-to-no-existing": "", + "email-sent-to": "", + "change-email-private": "", + "device-updated": "", + "device-created": "", + "confirm-regen-covers": "", + "alert-long-running": "", + "confirm-delete-multiple-series": "", + "confirm-delete-series": "", + "alert-bad-theme": "", + "confirm-library-delete": "", + "confirm-library-type-change": "", + "confirm-download-size": "", + "list-doesnt-exist": "" + }, + "actionable": { + "scan-library": "", + "refresh-covers": "", + "analyze-files": "", + "settings": "", + "edit": "", + "mark-as-read": "", + "mark-as-unread": "", + "scan-series": "", + "add-to": "", + "add-to-want-to-read": "", + "remove-from-want-to-read": "", + "remove-from-on-deck": "", + "others": "", + "add-to-reading-list": "", + "add-to-collection": "", + "send-to": "", + "delete": "", + "download": "", + "read-incognito": "", + "details": "", + "view-series": "", + "clear": "", + "import-cbl": "", + "read": "", + "add-rule-group-and": "", + "add-rule-group-or": "", + "remove-rule-group": "" + }, + "preferences": { + "left-to-right": "", + "right-to-left": "", + "horizontal": "", + "vertical": "", + "automatic": "", + "fit-to-height": "", + "fit-to-width": "", + "original": "", + "fit-to-screen": "", + "no-split": "", + "webtoon": "", + "single": "", + "double": "", + "double-manga": "", + "scroll": "", + "1-column": "", + "2-column": "", + "cards": "", + "list": "", + "up-to-down": "" + }, + "validation": { + "required-field": "", + "valid-email": "", + "password-validation": "" + }, + "entity-type": { + "volume": "", + "chapter": "", + "series": "", + "bookmark": "", + "logs": "" + }, + "common": { + "reset-to-default": "", + "close": "", + "cancel": "", + "create": "", + "save": "", + "reset": "", + "add": "", + "apply": "", + "delete": "", + "edit": "", + "help": "", + "submit": "", + "email": "", + "read": "", + "loading": "", + "username": "", + "password": "", + "promoted": "", + "select-all": "", + "deselect-all": "", + "series-count": "", + "item-count": "", + "book-num": "", + "issue-hash-num": "", + "issue-num": "", + "chapter-num": "", + "volume-num": "" + } +} diff --git a/UI/Web/src/main.ts b/UI/Web/src/main.ts index 3b14fd93a5..69514615e3 100644 --- a/UI/Web/src/main.ts +++ b/UI/Web/src/main.ts @@ -54,6 +54,24 @@ export const preLoad = { deps: [AccountService, TranslocoService] }; +function transformLanguageCodes(arr: Array) { + const transformedArray: Array = []; + + arr.forEach(code => { + // Add the original code + transformedArray.push(code); + + // Check if the code has a hyphen (like uk-UA) + if (code.includes('-')) { + // Transform hyphen to underscore and add to the array + const transformedCode = code.replace('-', '_'); + transformedArray.push(transformedCode); + } + }); + + return transformedArray; +} + // All Languages Kavita will support: http://www.lingoes.net/en/translator/langcode.htm const languageCodes = [ 'af', 'af-ZA', 'ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', @@ -78,13 +96,13 @@ const languageCodes = [ 'syr', 'syr-SY', 'ta', 'ta-IN', 'te', 'te-IN', 'th', 'th-TH', 'tl', 'tl-PH', 'tn', 'tn-ZA', 'tr', 'tr-TR', 'tt', 'tt-RU', 'ts', 'uk', 'uk-UA', 'ur', 'ur-PK', 'uz', 'uz-UZ', 'uz-UZ', 'vi', 'vi-VN', 'xh', 'xh-ZA', 'zh', 'zh-CN', 'zh-HK', 'zh-MO', - 'zh-SG', 'zh-TW', 'zu', 'zu-ZA', 'zh_Hans' + 'zh-SG', 'zh-TW', 'zu', 'zu-ZA', 'zh_Hans', ]; const translocoOptions = { config: { reRenderOnLangChange: true, - availableLangs: languageCodes, + availableLangs: transformLanguageCodes(languageCodes), prodMode: environment.production, defaultLang: 'en', fallbackLang: 'en', diff --git a/UI/Web/src/styles.scss b/UI/Web/src/styles.scss index 313f42126a..7bb064643c 100644 --- a/UI/Web/src/styles.scss +++ b/UI/Web/src/styles.scss @@ -40,6 +40,7 @@ @import './theme/components/offcanvas'; @import './theme/components/table'; @import './theme/components/alerts'; +@import './theme/components/typeahead'; @import './theme/utilities/utilities'; diff --git a/UI/Web/src/theme/components/_typeahead.scss b/UI/Web/src/theme/components/_typeahead.scss new file mode 100644 index 0000000000..e81d2e7986 --- /dev/null +++ b/UI/Web/src/theme/components/_typeahead.scss @@ -0,0 +1,68 @@ +:root { + /* size */ + --select2-single-height: 36px; + --select2-multiple-height: 36px; + + /* label */ + --select2-label-text-color: #000; + --select2-required-color: red; + + /* selection */ + --select2-selection-border-radius: 4px; + --select2-selection-background: var(--input-bg-readonly-color); + --select2-selection-disabled-background: #eee; + --select2-selection-border-color: var(--input-border-color); + --select2-selection-focus-border-color: var(--input-focus-boxshadow-color); + --select2-selection-text-color: var(--input-text-color); + + /* selection: choice item (multiple) */ + --select2-selection-choice-background: var(--tagbadge-filled-bg-color); + --select2-selection-choice-text-color: var(--tagbadge-filled-text-color); + --select2-selection-choice-border-color: var(--tagbadge-border-color); + --select2-selection-choice-close-color: var(--tagbadge-filled-text-color); + --select2-selection-choice-hover-close-color: var(--tagbadge-filled-text-color); + + /* placeholder */ + --select2-placeholder-color: #999; + --select2-placeholder-overflow: ellipsis; + + /* no result message */ + --select2-no-result-color: #888; + --select2-no-result-font-style: italic; + + /* no result message */ + --select2-too-much-result-color: #888; + --select2-too-much-result-style: italic; + + /* reset */ + --select2-reset-color: #999; + + /* arrow */ + --select2-arrow-color: #ccc; + + /* dropdown panel */ + --select2-dropdown-background: var(--input-bg-readonly-color); + --select2-dropdown-border-color: var(--input-border-color); + + /* overlay */ + --select2-overlay-backdrop: transparent; + + /* search field */ + --select2-search-border-color: var(--input-border-color); + --select2-search-background: var(--input-bg-readonly-color); + --select2-search-border-radius: 0px; + + /* dropdown option */ + --select2-option-text-color: var(--body-text-color); + --select2-option-disabled-text-color: #999; + --select2-option-disabled-background: transparent; + --select2-option-selected-text-color: lightgrey; + --select2-option-selected-background: var(--btn-disabled-bg-color); + --select2-option-highlighted-text-color: #fff; + --select2-option-highlighted-background: #5897fb; // TODO: This needs to be done correctly and applied throughout the app + --select2-option-group-text-color: var(--body-text-color); + --select2-option-group-background: transparent; + + /* hint */ + --select2-hint-text-color: #888; +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e75c9b0c00..0000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3' -services: - kavita: - image: kizaing/kavita:latest - container_name: kavita - volumes: - - ./manga:/manga - - ./config:/kavita/config - ports: - - "5000:5000" - restart: unless-stopped - - #Uncomment if you want to implement healthchecks - #healthcheck: - # test: curl --fail http://localhost:5000 || exit 1 - # interval: 300s - # retries: 3 - # start_period: 30s - # timeout: 15s diff --git a/entrypoint.sh b/entrypoint.sh index 2f55d5e3a5..798d19fd4c 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -27,11 +27,9 @@ if [ ! -f "/kavita/config/appsettings.json" ]; then fi fi -echo "App setting permissions" +echo "Starting Kavita" echo ls -l "/kavita/config/appsettings.json" -chmod +x Kavita - ./Kavita #if [[ "$PUID" -eq 0 ]]; then diff --git a/openapi.json b/openapi.json index fc9c842139..2e27f3fbd2 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.6.15" + "version": "0.7.8.1" }, "servers": [ { @@ -1268,7 +1268,7 @@ "tags": [ "Collection" ], - "summary": "Return a list of all collection tags on the server", + "summary": "Return a list of all collection tags on the server for the logged in user.", "responses": { "200": { "description": "Success", @@ -1300,6 +1300,28 @@ } } } + }, + "delete": { + "tags": [ + "Collection" + ], + "summary": "Removes the collection tag from all Series it was attached to", + "parameters": [ + { + "name": "tagId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success" + } + } } }, "/api/Collection/search": { @@ -1909,6 +1931,95 @@ } } }, + "/api/Filter": { + "get": { + "tags": [ + "Filter" + ], + "parameters": [ + { + "name": "name", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + } + } + } + } + } + }, + "/api/Filter/create-temp": { + "post": { + "tags": [ + "Filter" + ], + "summary": "Caches the filter in the backend and returns a temp string for retrieving.", + "description": "The cache line lives for only 1 hour", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/api/Health": { "get": { "tags": [ @@ -2942,6 +3053,69 @@ } } }, + "/api/Metadata/people-by-role": { + "get": { + "tags": [ + "Metadata" + ], + "summary": "Fetches people from the instance by role", + "parameters": [ + { + "name": "role", + "in": "query", + "description": "role", + "schema": { + "enum": [ + 1, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12 + ], + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonDto" + } + } + } + } + } + } + } + }, "/api/Metadata/people": { "get": { "tags": [ @@ -3333,6 +3507,37 @@ } } }, + "/api/Opds/{apiKey}/want-to-read": { + "get": { + "tags": [ + "Opds" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pageNumber", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success" + } + } + } + }, "/api/Opds/{apiKey}/collections": { "get": { "tags": [ @@ -4839,17 +5044,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FilterDto" + "$ref": "#/components/schemas/FilterV2Dto" } }, "text/json": { "schema": { - "$ref": "#/components/schemas/FilterDto" + "$ref": "#/components/schemas/FilterV2Dto" } }, "application/*+json": { "schema": { - "$ref": "#/components/schemas/FilterDto" + "$ref": "#/components/schemas/FilterV2Dto" } } } @@ -7180,10 +7385,13 @@ "tags": [ "Series" ], + "summary": "Gets series with the applied Filter", + "description": "This is considered v1 and no longer used by Kavita, but will be supported for sometime. See series/v2", "parameters": [ { "name": "libraryId", "in": "query", + "description": "", "schema": { "type": "integer", "format": "int32" @@ -7208,6 +7416,7 @@ } ], "requestBody": { + "description": "", "content": { "application/json": { "schema": { @@ -7226,6 +7435,85 @@ } } }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Series" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Series" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Series" + } + } + } + } + } + }, + "deprecated": true + } + }, + "/api/Series/v2": { + "post": { + "tags": [ + "Series" + ], + "summary": "Gets series with the applied Filter", + "parameters": [ + { + "name": "PageNumber", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "description": "If set to 0, will set as MaxInt", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -7595,20 +7883,226 @@ } } } - }, - "responses": { - "200": { - "description": "Success" - } + }, + "responses": { + "200": { + "description": "Success" + } + } + } + }, + "/api/Series/recently-added": { + "post": { + "tags": [ + "Series" + ], + "summary": "Gets all recently added series. Obsolete, use recently-added-v2", + "parameters": [ + { + "name": "PageNumber", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "description": "If set to 0, will set as MaxInt", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "libraryId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilterDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FilterDto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FilterDto" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + } + } + } + }, + "deprecated": true + } + }, + "/api/Series/recently-added-v2": { + "post": { + "tags": [ + "Series" + ], + "summary": "Gets all recently added series", + "parameters": [ + { + "name": "PageNumber", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "description": "If set to 0, will set as MaxInt", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + } + } + } + } + } + }, + "/api/Series/recently-updated-series": { + "post": { + "tags": [ + "Series" + ], + "summary": "Returns series that were recently updated, like adding or removing a chapter", + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecentlyAddedItemDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecentlyAddedItemDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecentlyAddedItemDto" + } + } + } + } + } } } }, - "/api/Series/recently-added": { + "/api/Series/all-v2": { "post": { "tags": [ "Series" ], - "summary": "Gets all recently added series", + "summary": "Returns all series for the library", "parameters": [ { "name": "PageNumber", @@ -7643,17 +8137,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FilterDto" + "$ref": "#/components/schemas/FilterV2Dto" } }, "text/json": { "schema": { - "$ref": "#/components/schemas/FilterDto" + "$ref": "#/components/schemas/FilterV2Dto" } }, "application/*+json": { "schema": { - "$ref": "#/components/schemas/FilterDto" + "$ref": "#/components/schemas/FilterV2Dto" } } } @@ -7691,51 +8185,12 @@ } } }, - "/api/Series/recently-updated-series": { - "post": { - "tags": [ - "Series" - ], - "summary": "Returns series that were recently updated, like adding or removing a chapter", - "responses": { - "200": { - "description": "Success", - "content": { - "text/plain": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RecentlyAddedItemDto" - } - } - }, - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RecentlyAddedItemDto" - } - } - }, - "text/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RecentlyAddedItemDto" - } - } - } - } - } - } - } - }, "/api/Series/all": { "post": { "tags": [ "Series" ], - "summary": "Returns all series for the library", + "summary": "Returns all series for the library. Obsolete, use all-v2", "parameters": [ { "name": "PageNumber", @@ -7815,7 +8270,8 @@ } } } - } + }, + "deprecated": true } }, "/api/Series/on-deck": { @@ -7853,26 +8309,6 @@ } } ], - "requestBody": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FilterDto" - } - }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/FilterDto" - } - }, - "application/*+json": { - "schema": { - "$ref": "#/components/schemas/FilterDto" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -9593,6 +10029,17 @@ "tags": [ "Stats" ], + "parameters": [ + { + "name": "userId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + ], "responses": { "200": { "description": "Success", @@ -10545,7 +10992,7 @@ "tags": [ "WantToRead" ], - "summary": "Return all Series that are in the current logged in user's Want to Read list, filtered", + "summary": "Return all Series that are in the current logged in user's Want to Read list, filtered (deprecated, use v2)", "parameters": [ { "name": "PageNumber", @@ -10615,7 +11062,8 @@ } } } - } + }, + "deprecated": true }, "get": { "tags": [ @@ -10655,6 +11103,84 @@ } } }, + "/api/want-to-read/v2": { + "post": { + "tags": [ + "WantToRead" + ], + "summary": "Return all Series that are in the current logged in user's Want to Read list, filtered", + "parameters": [ + { + "name": "PageNumber", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "description": "If set to 0, will set as MaxInt", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FilterV2Dto" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesDto" + } + } + } + } + } + } + } + }, "/api/want-to-read/add-series": { "post": { "tags": [ @@ -11589,6 +12115,9 @@ "chapterId": { "type": "integer", "format": "int32" + }, + "series": { + "$ref": "#/components/schemas/SeriesDto" } }, "additionalProperties": false @@ -13209,6 +13738,106 @@ }, "additionalProperties": false }, + "FilterStatementDto": { + "type": "object", + "properties": { + "comparison": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "type": "integer", + "format": "int32" + }, + "field": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25 + ], + "type": "integer", + "description": "Represents the field which will dictate the value type and the Extension used for filtering", + "format": "int32" + }, + "value": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "FilterV2Dto": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the filter", + "nullable": true + }, + "statements": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FilterStatementDto" + }, + "nullable": true + }, + "combination": { + "enum": [ + 0, + 1 + ], + "type": "integer", + "format": "int32" + }, + "sortOptions": { + "$ref": "#/components/schemas/SortOptions" + }, + "limitTo": { + "type": "integer", + "description": "Limit the number of rows returned. Defaults to not applying a limit (aka 0)", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Metadata filtering for v2 API only" + }, "FolderPath": { "type": "object", "properties": { @@ -15354,10 +15983,6 @@ "type": "string", "nullable": true }, - "summary": { - "type": "string", - "nullable": true - }, "pages": { "type": "integer", "format": "int32" @@ -16224,6 +16849,17 @@ "type": "integer", "description": "How many Days since today in the past for chapter updates, should content be considered for On Deck, before it gets removed automatically", "format": "int32" + }, + "coverImageSize": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "integer", + "description": "How large the cover images should be", + "format": "int32" } }, "additionalProperties": false @@ -17757,6 +18393,10 @@ "name": "Download", "description": "All APIs related to downloading entities from the system. Requires Download Role or Admin Role." }, + { + "name": "Filter", + "description": "This is responsible for Filter caching" + }, { "name": "Image", "description": "Responsible for servicing up images stored in Kavita for entities"