Localization - First Pass (#2174)
* Started designing the backend localization service * Worked in Transloco for initial PoC * Worked in Transloco for initial PoC * Translated the login screen * translated dashboard screen * Started work on the backend * Fixed a logic bug * translated edit-user screen * Hooked up the backend for having a locale property. * Hooked up the ability to view the available locales and switch to them. * Made the localization service languages be derived from what's in langs/ directory. * Fixed up localization switching * Switched when we check for a license on UI bootstrap * Tweaked some code * Fixed the bug where dashboard wasn't loading and made it so language switching is working. * Fixed a bug on dashboard with languagePath * Converted user-scrobble-history.component.html * Converted spoiler.component.html * Converted review-series-modal.component.html * Converted review-card-modal.component.html * Updated the readme * Translated using Weblate (English) Currently translated at 100.0% (54 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/en/ * Converted review-card.component.html * Deleted dead component * Converted want-to-read.component.html * Added translation using Weblate (Korean) * Translated using Weblate (Spanish) Currently translated at 40.7% (22 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/es/ * Translated using Weblate (Korean) Currently translated at 62.9% (34 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/ * Converted user-preferences.component.html * Translated using Weblate (Korean) Currently translated at 92.5% (50 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/ * Converted user-holds.component.html * Converted theme-manager.component.html * Converted restriction-selector.component.html * Converted manage-devices.component.html * Converted edit-device.component.html * Converted change-password.component.html * Converted change-email.component.html * Converted change-age-restriction.component.html * Converted api-key.component.html * Converted anilist-key.component.html * Converted typeahead.component.html * Converted user-stats-info-cards.component.html * Converted user-stats.component.html * Converted top-readers.component.html * Converted some pipes and ensure translation is loaded before the app. * Finished all but one pipe for localization * Converted directory-picker.component.html * Converted library-access-modal.component.html * Converted a few components * Converted a few components * Converted a few components * Converted a few components * Converted a few components * Merged weblate in * ... -> … update * Updated the readme * Updateded all fonts to be woff2 * Cleaned up some strings to increase re-use * Removed an old flow (that doesn't exist in backend any longer) from when we introduced emails on Kavita. * Converted Series detail * Lots more converted * Lots more converted & hooked up the ability to flatten during prod build the language files. * Lots more converted * Lots more converted & fixed a bunch of broken pipes due to inject() * Lots more converted * Lots more converted * Lots more converted & fixed some bad keys * Lots more converted * Fixed some bugs with admin dasbhoard nested tabs not rendering on first load due to not using onpush change detection * Fixed up some localization errors and fixed forgot password error when the user doesn't have change password permission * Fixed a stupid build issue again * Started adding errors for interceptor and backend. * Finished off manga-reader * More translations * Few fixes * Fixed a bug where character tag badges weren't showing the name on chapter info * All components are translated * All toasts are translated * All confirm/alerts are translated * Trying something new for the backend * Migrated the localization strings for the backend into a new file. * Updated the localization service to be able to do backend localization with fallback to english. * Cleaned up some external reviews code to reduce looping * Localized AccountController.cs * 60% done with controllers * All controllers are done * All KavitaExceptions are covered * Some shakeout fixes * Prep for initial merge * Everything is done except options and basic shakeout proves response times are good. Unit tests are broken. * Fixed up the unit tests * All unit tests are now working * Removed some quantifier * I'm not sure I can support localization for some Volume/Chapter/Book strings within the codebase. --------- Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> Co-authored-by: majora2007 <kavitareader@gmail.com> Co-authored-by: expertjun <jtrobin@naver.com> Co-authored-by: ThePromidius <thepromidiusyt@gmail.com>
This commit is contained in:
parent
670bf82c38
commit
3b23d63234
389 changed files with 13652 additions and 7925 deletions
|
@ -17,7 +17,6 @@ using API.Entities.Enums;
|
|||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Services;
|
||||
using EasyCaching.Core;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -37,6 +36,7 @@ public class OpdsController : BaseApiController
|
|||
private readonly IReaderService _readerService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
|
||||
private readonly XmlSerializer _xmlSerializer;
|
||||
|
@ -71,7 +71,7 @@ public class OpdsController : BaseApiController
|
|||
public OpdsController(IUnitOfWork unitOfWork, IDownloadService downloadService,
|
||||
IDirectoryService directoryService, ICacheService cacheService,
|
||||
IReaderService readerService, ISeriesService seriesService,
|
||||
IAccountService accountService, IEasyCachingProvider provider)
|
||||
IAccountService accountService, ILocalizationService localizationService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_downloadService = downloadService;
|
||||
|
@ -80,6 +80,7 @@ public class OpdsController : BaseApiController
|
|||
_readerService = readerService;
|
||||
_seriesService = seriesService;
|
||||
_accountService = accountService;
|
||||
_localizationService = localizationService;
|
||||
|
||||
_xmlSerializer = new XmlSerializer(typeof(Feed));
|
||||
_xmlOpenSearchSerializer = new XmlSerializer(typeof(OpenSearchDescription));
|
||||
|
@ -90,8 +91,9 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> Get(string apiKey)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
|
||||
|
@ -100,10 +102,10 @@ public class OpdsController : BaseApiController
|
|||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = "onDeck",
|
||||
Title = "On Deck",
|
||||
Title = await _localizationService.Translate(userId, "on-deck"),
|
||||
Content = new FeedEntryContent()
|
||||
{
|
||||
Text = "Browse by On Deck"
|
||||
Text = await _localizationService.Translate(userId, "browse-on-deck")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
|
@ -113,10 +115,10 @@ public class OpdsController : BaseApiController
|
|||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = "recentlyAdded",
|
||||
Title = "Recently Added",
|
||||
Title = await _localizationService.Translate(userId, "recently-added"),
|
||||
Content = new FeedEntryContent()
|
||||
{
|
||||
Text = "Browse by Recently Added"
|
||||
Text = await _localizationService.Translate(userId, "browse-recently-added")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
|
@ -126,10 +128,10 @@ public class OpdsController : BaseApiController
|
|||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = "readingList",
|
||||
Title = "Reading Lists",
|
||||
Title = await _localizationService.Translate(userId, "reading-lists"),
|
||||
Content = new FeedEntryContent()
|
||||
{
|
||||
Text = "Browse by Reading Lists"
|
||||
Text = await _localizationService.Translate(userId, "browse-reading-lists")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
|
@ -139,10 +141,10 @@ public class OpdsController : BaseApiController
|
|||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = "allLibraries",
|
||||
Title = "All Libraries",
|
||||
Title = await _localizationService.Translate(userId, "libraries"),
|
||||
Content = new FeedEntryContent()
|
||||
{
|
||||
Text = "Browse by Libraries"
|
||||
Text = await _localizationService.Translate(userId, "browse-libraries")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
|
@ -152,10 +154,10 @@ public class OpdsController : BaseApiController
|
|||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = "allCollections",
|
||||
Title = "All Collections",
|
||||
Title = await _localizationService.Translate(userId, "collections"),
|
||||
Content = new FeedEntryContent()
|
||||
{
|
||||
Text = "Browse by Collections"
|
||||
Text = await _localizationService.Translate(userId, "browse-collections")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
|
@ -183,12 +185,12 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetLibraries(string apiKey)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 libraries = await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId);
|
||||
var feed = CreateFeed("All Libraries", $"{prefix}{apiKey}/libraries", apiKey, prefix);
|
||||
var feed = CreateFeed(await _localizationService.Translate(userId, "libraries"), $"{prefix}{apiKey}/libraries", apiKey, prefix);
|
||||
SetFeedId(feed, "libraries");
|
||||
foreach (var library in libraries)
|
||||
{
|
||||
|
@ -210,10 +212,10 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetCollections(string apiKey)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
if (user == null) return Unauthorized();
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||
|
@ -222,7 +224,7 @@ public class OpdsController : BaseApiController
|
|||
: (await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync(userId));
|
||||
|
||||
|
||||
var feed = CreateFeed("All Collections", $"{prefix}{apiKey}/collections", apiKey, prefix);
|
||||
var feed = CreateFeed(await _localizationService.Translate(userId, "collections"), $"{prefix}{apiKey}/collections", apiKey, prefix);
|
||||
SetFeedId(feed, "collections");
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
|
@ -248,10 +250,10 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetCollection(int collectionId, string apiKey, [FromQuery] int pageNumber = 0)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
if (user == null) return Unauthorized();
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||
|
@ -292,10 +294,10 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetReadingLists(string apiKey, [FromQuery] int pageNumber = 0)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId,
|
||||
true, GetUserParams(pageNumber), false);
|
||||
|
@ -333,10 +335,10 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetReadingListItems(int readingListId, string apiKey)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
|
||||
var userWithLists = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user!.UserName!, AppUserIncludes.ReadingListsWithItems);
|
||||
|
@ -344,10 +346,10 @@ public class OpdsController : BaseApiController
|
|||
var readingList = userWithLists.ReadingLists.SingleOrDefault(t => t.Id == readingListId);
|
||||
if (readingList == null)
|
||||
{
|
||||
return BadRequest("Reading list does not exist or you don't have access");
|
||||
return BadRequest(await _localizationService.Translate(userId, "reading-list-restricted"));
|
||||
}
|
||||
|
||||
var feed = CreateFeed(readingList.Title + " Reading List", $"{prefix}{apiKey}/reading-list/{readingListId}", apiKey, prefix);
|
||||
var feed = CreateFeed(readingList.Title + " " + await _localizationService.Translate(userId, "reading-list"), $"{prefix}{apiKey}/reading-list/{readingListId}", apiKey, prefix);
|
||||
SetFeedId(feed, $"reading-list-{readingListId}");
|
||||
|
||||
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).ToList();
|
||||
|
@ -364,16 +366,16 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetSeriesForLibrary(int libraryId, string apiKey, [FromQuery] int pageNumber = 0)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 library =
|
||||
(await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).SingleOrDefault(l =>
|
||||
l.Id == libraryId);
|
||||
if (library == null)
|
||||
{
|
||||
return BadRequest("User does not have access to this library");
|
||||
return BadRequest(await _localizationService.Translate(userId, "no-library-access"));
|
||||
}
|
||||
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, GetUserParams(pageNumber), _filterDto);
|
||||
|
@ -395,14 +397,14 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetRecentlyAdded(string apiKey, [FromQuery] int pageNumber = 1)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 recentlyAdded = await _unitOfWork.SeriesRepository.GetRecentlyAdded(0, userId, GetUserParams(pageNumber), _filterDto);
|
||||
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(recentlyAdded.Select(s => s.Id));
|
||||
|
||||
var feed = CreateFeed("Recently Added", $"{prefix}{apiKey}/recently-added", apiKey, prefix);
|
||||
var feed = CreateFeed(await _localizationService.Translate(userId, "recently-added"), $"{prefix}{apiKey}/recently-added", apiKey, prefix);
|
||||
SetFeedId(feed, "recently-added");
|
||||
AddPagination(feed, recentlyAdded, $"{prefix}{apiKey}/recently-added");
|
||||
|
||||
|
@ -418,19 +420,19 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetOnDeck(string apiKey, [FromQuery] int pageNumber = 1)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
|
||||
var userId = await GetUser(apiKey);
|
||||
var userParams = GetUserParams(pageNumber);
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetOnDeck(userId, 0, userParams, _filterDto);
|
||||
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(pagedList.Select(s => s.Id));
|
||||
|
||||
Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages);
|
||||
|
||||
var feed = CreateFeed("On Deck", $"{prefix}{apiKey}/on-deck", apiKey, prefix);
|
||||
var feed = CreateFeed(await _localizationService.Translate(userId, "on-deck"), $"{prefix}{apiKey}/on-deck", apiKey, prefix);
|
||||
SetFeedId(feed, "on-deck");
|
||||
AddPagination(feed, pagedList, $"{prefix}{apiKey}/on-deck");
|
||||
|
||||
|
@ -446,20 +448,20 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> SearchSeries(string apiKey, [FromQuery] string query)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return BadRequest("You must pass a query parameter");
|
||||
return BadRequest(await _localizationService.Translate(userId, "query-required"));
|
||||
}
|
||||
query = query.Replace(@"%", string.Empty);
|
||||
// Get libraries user has access to
|
||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).ToList();
|
||||
if (!libraries.Any()) return BadRequest("User does not have access to any libraries");
|
||||
if (!libraries.Any()) return BadRequest(await _localizationService.Translate(userId, "libraries-restricted"));
|
||||
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||
|
||||
|
@ -518,13 +520,14 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetSearchDescriptor(string apiKey)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
var (_, prefix) = await GetPrefix();
|
||||
var feed = new OpenSearchDescription()
|
||||
{
|
||||
ShortName = "Search",
|
||||
Description = "Search for Series, Collections, or Reading Lists",
|
||||
ShortName = await _localizationService.Translate(userId, "search"),
|
||||
Description = await _localizationService.Translate(userId, "search-description"),
|
||||
Url = new SearchLink()
|
||||
{
|
||||
Type = FeedLinkType.AtomAcquisition,
|
||||
|
@ -542,13 +545,13 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetSeries(string apiKey, int seriesId)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
|
||||
|
||||
var feed = CreateFeed(series.Name + " - Storyline", $"{prefix}{apiKey}/series/{series.Id}", apiKey, prefix);
|
||||
var feed = CreateFeed(series!.Name + " - Storyline", $"{prefix}{apiKey}/series/{series.Id}", apiKey, prefix);
|
||||
SetFeedId(feed, $"series-{series.Id}");
|
||||
feed.Links.Add(CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesId}&apiKey={apiKey}"));
|
||||
|
||||
|
@ -564,7 +567,7 @@ public class OpdsController : BaseApiController
|
|||
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
|
||||
foreach (var mangaFile in files)
|
||||
{
|
||||
feed.Entries.Add(await CreateChapterWithFile(seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,7 +579,7 @@ public class OpdsController : BaseApiController
|
|||
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(storylineChapter.Id);
|
||||
foreach (var mangaFile in files)
|
||||
{
|
||||
feed.Entries.Add(await CreateChapterWithFile(seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -586,7 +589,7 @@ public class OpdsController : BaseApiController
|
|||
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(special.Id);
|
||||
foreach (var mangaFile in files)
|
||||
{
|
||||
feed.Entries.Add(await CreateChapterWithFile(seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -597,26 +600,26 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetVolume(string apiKey, int seriesId, int volumeId)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
|
||||
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
|
||||
var chapters =
|
||||
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number),
|
||||
_chapterSortComparer);
|
||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ",
|
||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {_seriesService.FormatChapterName(userId, libraryType)}s ",
|
||||
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey, prefix);
|
||||
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{SeriesService.FormatChapterName(libraryType)}s");
|
||||
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{_seriesService.FormatChapterName(userId, libraryType)}s");
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapter.Id);
|
||||
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
|
||||
foreach (var mangaFile in files)
|
||||
{
|
||||
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,23 +630,23 @@ public class OpdsController : BaseApiController
|
|||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetChapter(string apiKey, int seriesId, int volumeId, int chapterId)
|
||||
{
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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 series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
|
||||
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
||||
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId);
|
||||
if (chapter == null) return BadRequest("Chapter doesn't exist");
|
||||
if (chapter == null) return BadRequest(await _localizationService.Translate(userId, "chapter-doesnt-exist"));
|
||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
|
||||
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
|
||||
|
||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s",
|
||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {_seriesService.FormatChapterName(userId, libraryType)}s",
|
||||
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey, prefix);
|
||||
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{SeriesService.FormatChapterName(libraryType)}-{chapterId}-files");
|
||||
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{_seriesService.FormatChapterName(userId, libraryType)}-{chapterId}-files");
|
||||
foreach (var mangaFile in files)
|
||||
{
|
||||
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapterId, mangaFile, series, chapter, apiKey, prefix, baseUrl));
|
||||
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volumeId, chapterId, mangaFile, series, chapter, apiKey, prefix, baseUrl));
|
||||
}
|
||||
|
||||
return CreateXmlResult(SerializeXml(feed));
|
||||
|
@ -661,8 +664,9 @@ public class OpdsController : BaseApiController
|
|||
[HttpGet("{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}/download/{filename}")]
|
||||
public async Task<ActionResult> DownloadFile(string apiKey, int seriesId, int volumeId, int chapterId, string filename)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest("OPDS is not enabled on this server");
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(await GetUser(apiKey));
|
||||
if (!await _accountService.HasDownloadPermission(user))
|
||||
{
|
||||
|
@ -781,7 +785,7 @@ public class OpdsController : BaseApiController
|
|||
};
|
||||
}
|
||||
|
||||
private async Task<FeedEntry> CreateChapterWithFile(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey, string prefix, string baseUrl)
|
||||
private async Task<FeedEntry> CreateChapterWithFile(int userId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey, string prefix, string baseUrl)
|
||||
{
|
||||
var fileSize =
|
||||
mangaFile.Bytes > 0 ? DirectoryService.GetHumanReadableBytes(mangaFile.Bytes) :
|
||||
|
@ -797,7 +801,8 @@ public class OpdsController : BaseApiController
|
|||
|
||||
if (volume!.Chapters.Count == 1)
|
||||
{
|
||||
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
|
||||
var volumeLabel = await _localizationService.Translate(userId, "volume-num", string.Empty);
|
||||
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType, volumeLabel);
|
||||
if (volume.Name != "0")
|
||||
{
|
||||
title += $" - {volume.Name}";
|
||||
|
@ -805,11 +810,11 @@ public class OpdsController : BaseApiController
|
|||
}
|
||||
else if (volume.Number != 0)
|
||||
{
|
||||
title = $"{series.Name} - Volume {volume.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
|
||||
title = $"{series.Name} - Volume {volume.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
title = $"{series.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
|
||||
title = $"{series.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
|
||||
}
|
||||
|
||||
// Chunky requires a file at the end. Our API ignores this
|
||||
|
@ -857,14 +862,16 @@ public class OpdsController : BaseApiController
|
|||
[HttpGet("{apiKey}/image")]
|
||||
public async Task<ActionResult> GetPageStreamedImage(string apiKey, [FromQuery] int libraryId, [FromQuery] int seriesId, [FromQuery] int volumeId,[FromQuery] int chapterId, [FromQuery] int pageNumber)
|
||||
{
|
||||
if (pageNumber < 0) return BadRequest("Page cannot be less than 0");
|
||||
var userId = await GetUser(apiKey);
|
||||
if (pageNumber < 0) return BadRequest(await _localizationService.Translate(userId, "greater-0", "Page"));
|
||||
var chapter = await _cacheService.Ensure(chapterId);
|
||||
if (chapter == null) return BadRequest("There was an issue finding image file for reading");
|
||||
if (chapter == null) return BadRequest(await _localizationService.Translate(userId, "cache-file-find"));
|
||||
|
||||
try
|
||||
{
|
||||
var path = _cacheService.GetCachedPagePath(chapter.Id, pageNumber);
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {pageNumber}");
|
||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path))
|
||||
return BadRequest(await _localizationService.Translate(userId, "no-image-for-page", pageNumber));
|
||||
|
||||
var content = await _directoryService.ReadFileAsync(path);
|
||||
var format = Path.GetExtension(path);
|
||||
|
@ -895,8 +902,9 @@ public class OpdsController : BaseApiController
|
|||
[ResponseCache(Duration = 60 * 60, Location = ResponseCacheLocation.Client, NoStore = false)]
|
||||
public async Task<ActionResult> GetFavicon(string apiKey)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
var files = _directoryService.GetFilesWithExtension(Path.Join(Directory.GetCurrentDirectory(), ".."), @"\.ico");
|
||||
if (files.Length == 0) return BadRequest("Cannot find icon");
|
||||
if (files.Length == 0) return BadRequest(await _localizationService.Translate(userId, "favicon-doesnt-exist"));
|
||||
var path = files[0];
|
||||
var content = await _directoryService.ReadFileAsync(path);
|
||||
var format = Path.GetExtension(path);
|
||||
|
@ -919,7 +927,7 @@ public class OpdsController : BaseApiController
|
|||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
throw new KavitaException("User does not exist");
|
||||
throw new KavitaException(await _localizationService.Get("en", "user-doesnt-exist"));
|
||||
}
|
||||
|
||||
private async Task<FeedLink> CreatePageStreamLink(int libraryId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, string apiKey, string prefix)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue